@memberjunction/ng-conversations 5.40.2 → 5.41.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 (378) hide show
  1. package/README.md +57 -0
  2. package/dist/__tests__/channel-optional-surface.test.d.ts +2 -0
  3. package/dist/__tests__/channel-optional-surface.test.d.ts.map +1 -0
  4. package/dist/__tests__/channel-optional-surface.test.js +53 -0
  5. package/dist/__tests__/channel-optional-surface.test.js.map +1 -0
  6. package/dist/__tests__/chat-events.test.d.ts +14 -0
  7. package/dist/__tests__/chat-events.test.d.ts.map +1 -0
  8. package/dist/__tests__/chat-events.test.js +109 -0
  9. package/dist/__tests__/chat-events.test.js.map +1 -0
  10. package/dist/__tests__/conversation-naming.test.d.ts +2 -0
  11. package/dist/__tests__/conversation-naming.test.d.ts.map +1 -0
  12. package/dist/__tests__/conversation-naming.test.js +110 -0
  13. package/dist/__tests__/conversation-naming.test.js.map +1 -0
  14. package/dist/__tests__/delegation-result-parser.test.d.ts +2 -0
  15. package/dist/__tests__/delegation-result-parser.test.d.ts.map +1 -0
  16. package/dist/__tests__/delegation-result-parser.test.js +107 -0
  17. package/dist/__tests__/delegation-result-parser.test.js.map +1 -0
  18. package/dist/__tests__/event-wiring.test.d.ts +15 -0
  19. package/dist/__tests__/event-wiring.test.d.ts.map +1 -0
  20. package/dist/__tests__/event-wiring.test.js +100 -0
  21. package/dist/__tests__/event-wiring.test.js.map +1 -0
  22. package/dist/__tests__/narration-template.test.d.ts +2 -0
  23. package/dist/__tests__/narration-template.test.d.ts.map +1 -0
  24. package/dist/__tests__/narration-template.test.js +76 -0
  25. package/dist/__tests__/narration-template.test.js.map +1 -0
  26. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +2 -0
  27. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +1 -0
  28. package/dist/__tests__/realtime-agent-picker-models.test.js +49 -0
  29. package/dist/__tests__/realtime-agent-picker-models.test.js.map +1 -0
  30. package/dist/__tests__/realtime-audio-visuals.test.d.ts +2 -0
  31. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +1 -0
  32. package/dist/__tests__/realtime-audio-visuals.test.js +123 -0
  33. package/dist/__tests__/realtime-audio-visuals.test.js.map +1 -0
  34. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +2 -0
  35. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +1 -0
  36. package/dist/__tests__/realtime-delegation-card-cancel.test.js +48 -0
  37. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +1 -0
  38. package/dist/__tests__/realtime-disclosure.test.d.ts +2 -0
  39. package/dist/__tests__/realtime-disclosure.test.d.ts.map +1 -0
  40. package/dist/__tests__/realtime-disclosure.test.js +164 -0
  41. package/dist/__tests__/realtime-disclosure.test.js.map +1 -0
  42. package/dist/__tests__/realtime-pairing.test.d.ts +2 -0
  43. package/dist/__tests__/realtime-pairing.test.d.ts.map +1 -0
  44. package/dist/__tests__/realtime-pairing.test.js +207 -0
  45. package/dist/__tests__/realtime-pairing.test.js.map +1 -0
  46. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +2 -0
  47. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +1 -0
  48. package/dist/__tests__/realtime-review-lifecycle.test.js +154 -0
  49. package/dist/__tests__/realtime-review-lifecycle.test.js.map +1 -0
  50. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +2 -0
  51. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +1 -0
  52. package/dist/__tests__/realtime-session-cancel-usage.test.js +230 -0
  53. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +1 -0
  54. package/dist/__tests__/realtime-session-channels.test.d.ts +2 -0
  55. package/dist/__tests__/realtime-session-channels.test.d.ts.map +1 -0
  56. package/dist/__tests__/realtime-session-channels.test.js +252 -0
  57. package/dist/__tests__/realtime-session-channels.test.js.map +1 -0
  58. package/dist/__tests__/realtime-session-client-tools.test.d.ts +2 -0
  59. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +1 -0
  60. package/dist/__tests__/realtime-session-client-tools.test.js +103 -0
  61. package/dist/__tests__/realtime-session-client-tools.test.js.map +1 -0
  62. package/dist/__tests__/realtime-session-minimized.test.d.ts +2 -0
  63. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +1 -0
  64. package/dist/__tests__/realtime-session-minimized.test.js +32 -0
  65. package/dist/__tests__/realtime-session-minimized.test.js.map +1 -0
  66. package/dist/__tests__/realtime-session-mint.test.d.ts +2 -0
  67. package/dist/__tests__/realtime-session-mint.test.d.ts.map +1 -0
  68. package/dist/__tests__/realtime-session-mint.test.js +69 -0
  69. package/dist/__tests__/realtime-session-mint.test.js.map +1 -0
  70. package/dist/__tests__/realtime-session-policy.test.d.ts +2 -0
  71. package/dist/__tests__/realtime-session-policy.test.d.ts.map +1 -0
  72. package/dist/__tests__/realtime-session-policy.test.js +303 -0
  73. package/dist/__tests__/realtime-session-policy.test.js.map +1 -0
  74. package/dist/__tests__/realtime-session-review.service.test.d.ts +2 -0
  75. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +1 -0
  76. package/dist/__tests__/realtime-session-review.service.test.js +743 -0
  77. package/dist/__tests__/realtime-session-review.service.test.js.map +1 -0
  78. package/dist/__tests__/realtime-session-state.test.d.ts +2 -0
  79. package/dist/__tests__/realtime-session-state.test.d.ts.map +1 -0
  80. package/dist/__tests__/realtime-session-state.test.js +83 -0
  81. package/dist/__tests__/realtime-session-state.test.js.map +1 -0
  82. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +2 -0
  83. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +1 -0
  84. package/dist/__tests__/realtime-session-timeline-card.test.js +106 -0
  85. package/dist/__tests__/realtime-session-timeline-card.test.js.map +1 -0
  86. package/dist/__tests__/realtime-session-timeline.test.d.ts +2 -0
  87. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +1 -0
  88. package/dist/__tests__/realtime-session-timeline.test.js +142 -0
  89. package/dist/__tests__/realtime-session-timeline.test.js.map +1 -0
  90. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +19 -0
  91. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +1 -0
  92. package/dist/__tests__/realtime-sessions-adapter.test.js +188 -0
  93. package/dist/__tests__/realtime-sessions-adapter.test.js.map +1 -0
  94. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +2 -0
  95. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +1 -0
  96. package/dist/__tests__/realtime-surface-panel-prefs.test.js +100 -0
  97. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +1 -0
  98. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +2 -0
  99. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +1 -0
  100. package/dist/__tests__/realtime-surface-tabs-model.test.js +193 -0
  101. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +1 -0
  102. package/dist/__tests__/remote-browser-audio-player.test.d.ts +2 -0
  103. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +1 -0
  104. package/dist/__tests__/remote-browser-audio-player.test.js +137 -0
  105. package/dist/__tests__/remote-browser-audio-player.test.js.map +1 -0
  106. package/dist/__tests__/remote-browser-channel.test.d.ts +2 -0
  107. package/dist/__tests__/remote-browser-channel.test.d.ts.map +1 -0
  108. package/dist/__tests__/remote-browser-channel.test.js +423 -0
  109. package/dist/__tests__/remote-browser-channel.test.js.map +1 -0
  110. package/dist/__tests__/slot-defaults.test.d.ts +24 -0
  111. package/dist/__tests__/slot-defaults.test.d.ts.map +1 -0
  112. package/dist/__tests__/slot-defaults.test.js +63 -0
  113. package/dist/__tests__/slot-defaults.test.js.map +1 -0
  114. package/dist/__tests__/user-authorization.test.d.ts +2 -0
  115. package/dist/__tests__/user-authorization.test.d.ts.map +1 -0
  116. package/dist/__tests__/user-authorization.test.js +97 -0
  117. package/dist/__tests__/user-authorization.test.js.map +1 -0
  118. package/dist/__tests__/voice-session-narration.test.d.ts +2 -0
  119. package/dist/__tests__/voice-session-narration.test.d.ts.map +1 -0
  120. package/dist/__tests__/voice-session-narration.test.js +609 -0
  121. package/dist/__tests__/voice-session-narration.test.js.map +1 -0
  122. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +2 -0
  123. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +1 -0
  124. package/dist/__tests__/whiteboard-artifact-viewer.test.js +101 -0
  125. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +1 -0
  126. package/dist/__tests__/whiteboard-channel.test.d.ts +2 -0
  127. package/dist/__tests__/whiteboard-channel.test.d.ts.map +1 -0
  128. package/dist/__tests__/whiteboard-channel.test.js +260 -0
  129. package/dist/__tests__/whiteboard-channel.test.js.map +1 -0
  130. package/dist/__tests__/whiteboard-restore-state.test.d.ts +2 -0
  131. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +1 -0
  132. package/dist/__tests__/whiteboard-restore-state.test.js +108 -0
  133. package/dist/__tests__/whiteboard-restore-state.test.js.map +1 -0
  134. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +205 -3
  135. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  136. package/dist/lib/components/conversation/conversation-chat-area.component.js +911 -342
  137. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  138. package/dist/lib/components/mention/mention-dropdown.component.js +35 -17
  139. package/dist/lib/components/mention/mention-dropdown.component.js.map +1 -1
  140. package/dist/lib/components/mention/mention-editor.component.d.ts +4 -0
  141. package/dist/lib/components/mention/mention-editor.component.d.ts.map +1 -1
  142. package/dist/lib/components/mention/mention-editor.component.js +43 -19
  143. package/dist/lib/components/mention/mention-editor.component.js.map +1 -1
  144. package/dist/lib/components/message/message-input-box.component.d.ts +17 -1
  145. package/dist/lib/components/message/message-input-box.component.d.ts.map +1 -1
  146. package/dist/lib/components/message/message-input-box.component.js +73 -15
  147. package/dist/lib/components/message/message-input-box.component.js.map +1 -1
  148. package/dist/lib/components/message/message-input.component.d.ts +142 -6
  149. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  150. package/dist/lib/components/message/message-input.component.js +328 -82
  151. package/dist/lib/components/message/message-input.component.js.map +1 -1
  152. package/dist/lib/components/message/message-item.component.d.ts +28 -3
  153. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  154. package/dist/lib/components/message/message-item.component.js +180 -108
  155. package/dist/lib/components/message/message-item.component.js.map +1 -1
  156. package/dist/lib/components/message/message-list.component.d.ts +81 -2
  157. package/dist/lib/components/message/message-list.component.d.ts.map +1 -1
  158. package/dist/lib/components/message/message-list.component.js +252 -87
  159. package/dist/lib/components/message/message-list.component.js.map +1 -1
  160. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +282 -0
  161. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -0
  162. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +158 -0
  163. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -0
  164. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts +25 -0
  165. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.d.ts.map +1 -0
  166. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js +140 -0
  167. package/dist/lib/components/realtime/channels/channel-onboarding-panel.component.js.map +1 -0
  168. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts +35 -0
  169. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.d.ts.map +1 -0
  170. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js +58 -0
  171. package/dist/lib/components/realtime/channels/realtime-channel-pane.component.js.map +1 -0
  172. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +63 -0
  173. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -0
  174. package/dist/lib/components/realtime/realtime-activity-rail.component.js +260 -0
  175. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -0
  176. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +117 -0
  177. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -0
  178. package/dist/lib/components/realtime/realtime-agent-banner.component.js +504 -0
  179. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -0
  180. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +168 -0
  181. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -0
  182. package/dist/lib/components/realtime/realtime-agent-picker.component.js +556 -0
  183. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -0
  184. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +97 -0
  185. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -0
  186. package/dist/lib/components/realtime/realtime-audio-visuals.js +139 -0
  187. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -0
  188. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts +29 -0
  189. package/dist/lib/components/realtime/realtime-channel-strip.component.d.ts.map +1 -0
  190. package/dist/lib/components/realtime/realtime-channel-strip.component.js +69 -0
  191. package/dist/lib/components/realtime/realtime-channel-strip.component.js.map +1 -0
  192. package/dist/lib/components/realtime/realtime-composer.component.d.ts +65 -0
  193. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -0
  194. package/dist/lib/components/realtime/realtime-composer.component.js +256 -0
  195. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -0
  196. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +71 -0
  197. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -0
  198. package/dist/lib/components/realtime/realtime-delegation-card.component.js +324 -0
  199. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -0
  200. package/dist/lib/components/realtime/realtime-disclosure.d.ts +135 -0
  201. package/dist/lib/components/realtime/realtime-disclosure.d.ts.map +1 -0
  202. package/dist/lib/components/realtime/realtime-disclosure.js +188 -0
  203. package/dist/lib/components/realtime/realtime-disclosure.js.map +1 -0
  204. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +491 -0
  205. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -0
  206. package/dist/lib/components/realtime/realtime-session-overlay.component.js +1274 -0
  207. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -0
  208. package/dist/lib/components/realtime/realtime-session-state.d.ts +191 -0
  209. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -0
  210. package/dist/lib/components/realtime/realtime-session-state.js +244 -0
  211. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -0
  212. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts +56 -0
  213. package/dist/lib/components/realtime/realtime-session-thread.component.d.ts.map +1 -0
  214. package/dist/lib/components/realtime/realtime-session-thread.component.js +246 -0
  215. package/dist/lib/components/realtime/realtime-session-thread.component.js.map +1 -0
  216. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts +51 -0
  217. package/dist/lib/components/realtime/realtime-session-timeline-card.component.d.ts.map +1 -0
  218. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js +193 -0
  219. package/dist/lib/components/realtime/realtime-session-timeline-card.component.js.map +1 -0
  220. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts +77 -0
  221. package/dist/lib/components/realtime/realtime-surface-panel-prefs.d.ts.map +1 -0
  222. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js +114 -0
  223. package/dist/lib/components/realtime/realtime-surface-panel-prefs.js.map +1 -0
  224. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +173 -0
  225. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -0
  226. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +496 -0
  227. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -0
  228. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +181 -0
  229. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -0
  230. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +223 -0
  231. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -0
  232. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts +163 -0
  233. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.d.ts.map +1 -0
  234. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js +309 -0
  235. package/dist/lib/components/realtime/remote-browser/remote-browser-audio-player.js.map +1 -0
  236. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts +168 -0
  237. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.d.ts.map +1 -0
  238. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js +524 -0
  239. package/dist/lib/components/realtime/remote-browser/remote-browser-channel.js.map +1 -0
  240. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts +346 -0
  241. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.d.ts.map +1 -0
  242. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js +851 -0
  243. package/dist/lib/components/realtime/remote-browser/remote-browser-surface.component.js.map +1 -0
  244. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts +86 -0
  245. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.d.ts.map +1 -0
  246. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js +210 -0
  247. package/dist/lib/components/realtime/remote-browser/remote-browser-tools.js.map +1 -0
  248. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts +48 -0
  249. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.d.ts.map +1 -0
  250. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js +180 -0
  251. package/dist/lib/components/realtime/whiteboard/whiteboard-artifact-viewer.component.js.map +1 -0
  252. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts +119 -0
  253. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.d.ts.map +1 -0
  254. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js +274 -0
  255. package/dist/lib/components/realtime/whiteboard/whiteboard-channel.js.map +1 -0
  256. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts +11 -0
  257. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.d.ts.map +1 -0
  258. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js +98 -0
  259. package/dist/lib/components/slots/mj-chat-agent-presence-default.component.js.map +1 -0
  260. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts +9 -0
  261. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.d.ts.map +1 -0
  262. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js +35 -0
  263. package/dist/lib/components/slots/mj-chat-demonstration-surface-default.component.js.map +1 -0
  264. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts +28 -0
  265. package/dist/lib/components/slots/mj-chat-empty-state-default.component.d.ts.map +1 -0
  266. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js +104 -0
  267. package/dist/lib/components/slots/mj-chat-empty-state-default.component.js.map +1 -0
  268. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts +11 -0
  269. package/dist/lib/components/slots/mj-chat-header-default.component.d.ts.map +1 -0
  270. package/dist/lib/components/slots/mj-chat-header-default.component.js +103 -0
  271. package/dist/lib/components/slots/mj-chat-header-default.component.js.map +1 -0
  272. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts +15 -0
  273. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.d.ts.map +1 -0
  274. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js +73 -0
  275. package/dist/lib/components/slots/mj-chat-message-bubble-default.component.js.map +1 -0
  276. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts +9 -0
  277. package/dist/lib/components/slots/mj-chat-message-extra-default.component.d.ts.map +1 -0
  278. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js +34 -0
  279. package/dist/lib/components/slots/mj-chat-message-extra-default.component.js.map +1 -0
  280. package/dist/lib/components/slots/slot-interfaces.d.ts +95 -0
  281. package/dist/lib/components/slots/slot-interfaces.d.ts.map +1 -0
  282. package/dist/lib/components/slots/slot-interfaces.js +18 -0
  283. package/dist/lib/components/slots/slot-interfaces.js.map +1 -0
  284. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +11 -0
  285. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  286. package/dist/lib/components/workspace/conversation-workspace.component.js +28 -4
  287. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  288. package/dist/lib/conversations.module.d.ts +12 -1
  289. package/dist/lib/conversations.module.d.ts.map +1 -1
  290. package/dist/lib/conversations.module.js +93 -5
  291. package/dist/lib/conversations.module.js.map +1 -1
  292. package/dist/lib/directives/chat-slot.directive.d.ts +44 -0
  293. package/dist/lib/directives/chat-slot.directive.d.ts.map +1 -0
  294. package/dist/lib/directives/chat-slot.directive.js +54 -0
  295. package/dist/lib/directives/chat-slot.directive.js.map +1 -0
  296. package/dist/lib/events/chat-events.d.ts +137 -0
  297. package/dist/lib/events/chat-events.d.ts.map +1 -0
  298. package/dist/lib/events/chat-events.js +189 -0
  299. package/dist/lib/events/chat-events.js.map +1 -0
  300. package/dist/lib/models/conversation-state.model.d.ts +2 -1
  301. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  302. package/dist/lib/models/conversation-state.model.js.map +1 -1
  303. package/dist/lib/services/artifact-state.service.d.ts.map +1 -1
  304. package/dist/lib/services/artifact-state.service.js +23 -6
  305. package/dist/lib/services/artifact-state.service.js.map +1 -1
  306. package/dist/lib/services/conversation-agent.service.d.ts +60 -74
  307. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  308. package/dist/lib/services/conversation-agent.service.js +100 -313
  309. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  310. package/dist/lib/services/conversation-bridge.service.d.ts +11 -70
  311. package/dist/lib/services/conversation-bridge.service.d.ts.map +1 -1
  312. package/dist/lib/services/conversation-bridge.service.js +51 -85
  313. package/dist/lib/services/conversation-bridge.service.js.map +1 -1
  314. package/dist/lib/services/conversation-naming.d.ts +63 -0
  315. package/dist/lib/services/conversation-naming.d.ts.map +1 -0
  316. package/dist/lib/services/conversation-naming.js +58 -0
  317. package/dist/lib/services/conversation-naming.js.map +1 -0
  318. package/dist/lib/services/conversation-streaming.service.d.ts +24 -154
  319. package/dist/lib/services/conversation-streaming.service.d.ts.map +1 -1
  320. package/dist/lib/services/conversation-streaming.service.js +39 -361
  321. package/dist/lib/services/conversation-streaming.service.js.map +1 -1
  322. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts +10 -0
  323. package/dist/lib/services/conversations-runtime-bootstrap.service.d.ts.map +1 -0
  324. package/dist/lib/services/conversations-runtime-bootstrap.service.js +104 -0
  325. package/dist/lib/services/conversations-runtime-bootstrap.service.js.map +1 -0
  326. package/dist/lib/services/delegation-result-parser.d.ts +45 -0
  327. package/dist/lib/services/delegation-result-parser.d.ts.map +1 -0
  328. package/dist/lib/services/delegation-result-parser.js +48 -0
  329. package/dist/lib/services/delegation-result-parser.js.map +1 -0
  330. package/dist/lib/services/mention-autocomplete.service.d.ts +19 -4
  331. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  332. package/dist/lib/services/mention-autocomplete.service.js +65 -4
  333. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  334. package/dist/lib/services/mention-parser.service.d.ts +8 -53
  335. package/dist/lib/services/mention-parser.service.d.ts.map +1 -1
  336. package/dist/lib/services/mention-parser.service.js +32 -243
  337. package/dist/lib/services/mention-parser.service.js.map +1 -1
  338. package/dist/lib/services/narration-template.d.ts +42 -0
  339. package/dist/lib/services/narration-template.d.ts.map +1 -0
  340. package/dist/lib/services/narration-template.js +73 -0
  341. package/dist/lib/services/narration-template.js.map +1 -0
  342. package/dist/lib/services/realtime-pairing.d.ts +120 -0
  343. package/dist/lib/services/realtime-pairing.d.ts.map +1 -0
  344. package/dist/lib/services/realtime-pairing.js +150 -0
  345. package/dist/lib/services/realtime-pairing.js.map +1 -0
  346. package/dist/lib/services/realtime-session-review.service.d.ts +233 -0
  347. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -0
  348. package/dist/lib/services/realtime-session-review.service.js +417 -0
  349. package/dist/lib/services/realtime-session-review.service.js.map +1 -0
  350. package/dist/lib/services/realtime-session.service.d.ts +739 -0
  351. package/dist/lib/services/realtime-session.service.d.ts.map +1 -0
  352. package/dist/lib/services/realtime-session.service.js +1647 -0
  353. package/dist/lib/services/realtime-session.service.js.map +1 -0
  354. package/dist/lib/services/realtime-sessions-adapter.d.ts +54 -0
  355. package/dist/lib/services/realtime-sessions-adapter.d.ts.map +1 -0
  356. package/dist/lib/services/realtime-sessions-adapter.js +154 -0
  357. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -0
  358. package/dist/lib/services/user-authorization.d.ts +67 -0
  359. package/dist/lib/services/user-authorization.d.ts.map +1 -0
  360. package/dist/lib/services/user-authorization.js +66 -0
  361. package/dist/lib/services/user-authorization.js.map +1 -0
  362. package/dist/lib/utils/realtime-session-timeline.d.ts +84 -0
  363. package/dist/lib/utils/realtime-session-timeline.d.ts.map +1 -0
  364. package/dist/lib/utils/realtime-session-timeline.js +94 -0
  365. package/dist/lib/utils/realtime-session-timeline.js.map +1 -0
  366. package/dist/public-api.d.ts +41 -0
  367. package/dist/public-api.d.ts.map +1 -1
  368. package/dist/public-api.js +50 -0
  369. package/dist/public-api.js.map +1 -1
  370. package/package.json +27 -24
  371. package/dist/__tests__/conversation-bridge.service.test.d.ts +0 -2
  372. package/dist/__tests__/conversation-bridge.service.test.d.ts.map +0 -1
  373. package/dist/__tests__/conversation-bridge.service.test.js +0 -98
  374. package/dist/__tests__/conversation-bridge.service.test.js.map +0 -1
  375. package/dist/__tests__/mention-parser.test.d.ts +0 -2
  376. package/dist/__tests__/mention-parser.test.d.ts.map +0 -1
  377. package/dist/__tests__/mention-parser.test.js +0 -154
  378. package/dist/__tests__/mention-parser.test.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-session-timeline.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-session-timeline.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it, expect } from 'vitest';\nimport {\n BuildConversationTimeline,\n ConversationTimelineItem,\n RealtimeTimelineSourceDetail\n} from '../lib/utils/realtime-session-timeline';\n\n/**\n * The PURE grouping pass behind the conversation timeline's realtime SESSION BLOCKS:\n * details stamped with an `AgentSessionID` must NOT render as normal chat bubbles —\n * they collapse into ONE block per session at the session's chronological position,\n * while every unstamped message passes through untouched and in order.\n */\n\nlet nextId = 0;\n\nfunction detail(overrides: Partial<RealtimeTimelineSourceDetail> = {}): RealtimeTimelineSourceDetail {\n nextId++;\n return {\n ID: `D-${nextId}`,\n AgentSessionID: null,\n Role: 'User',\n Message: `message ${nextId}`,\n HiddenToUser: false,\n __mj_CreatedAt: new Date(2026, 5, 1, 10, 0, nextId),\n ...overrides\n };\n}\n\nfunction kinds(items: ConversationTimelineItem<RealtimeTimelineSourceDetail>[]): string[] {\n return items.map(i => i.Kind);\n}\n\ndescribe('BuildConversationTimeline — passthrough of normal messages', () => {\n it('returns an empty timeline for no details', () => {\n expect(BuildConversationTimeline([])).toEqual([]);\n });\n\n it('passes unstamped messages through unchanged, in order, by identity', () => {\n const a = detail();\n const b = detail({ Role: 'AI' });\n const items = BuildConversationTimeline([a, b]);\n\n expect(kinds(items)).toEqual(['message', 'message']);\n expect(items[0].Kind === 'message' && items[0].Detail).toBe(a);\n expect(items[1].Kind === 'message' && items[1].Detail).toBe(b);\n });\n\n it('treats empty/whitespace AgentSessionID stamps as unstamped', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: '' }),\n detail({ AgentSessionID: ' ' })\n ]);\n expect(kinds(items)).toEqual(['message', 'message']);\n });\n});\n\ndescribe('BuildConversationTimeline — session collapse', () => {\n it('collapses consecutive same-session details into ONE block at their chronological position', () => {\n const before = detail();\n const turn1 = detail({ AgentSessionID: 'S-1', Role: 'User', Message: 'hello there' });\n const turn2 = detail({ AgentSessionID: 'S-1', Role: 'AI', Message: 'hi! how can I help?' });\n const after = detail();\n\n const items = BuildConversationTimeline([before, turn1, turn2, after]);\n\n expect(kinds(items)).toEqual(['message', 'session', 'message']);\n const block = items[1];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.SessionID).toBe('S-1');\n expect(block.Group.TurnCount).toBe(2);\n expect(block.Group.DetailCount).toBe(2);\n expect(block.Group.StartedAt).toEqual(turn1.__mj_CreatedAt);\n expect(block.Group.EndedAt).toEqual(turn2.__mj_CreatedAt);\n expect(block.Group.LastTurnRole).toBe('Assistant'); // AI maps to Assistant\n expect(block.Group.LastTurnPreview).toBe('hi! how can I help?');\n });\n\n it('renders ONE element per session even when its rows are interleaved with normal messages', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'S-1', Message: 'first leg turn' }),\n detail({ Message: 'normal note in between' }),\n detail({ AgentSessionID: 'S-1', Role: 'AI', Message: 'late session turn' })\n ]);\n\n expect(kinds(items)).toEqual(['session', 'message']);\n const block = items[0];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.TurnCount).toBe(2);\n expect(block.Group.LastTurnPreview).toBe('late session turn');\n });\n\n it('produces separate blocks for DIFFERENT sessions, each at its own position', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'S-1' }),\n detail(),\n detail({ AgentSessionID: 'S-2' })\n ]);\n\n expect(kinds(items)).toEqual(['session', 'message', 'session']);\n const first = items[0];\n const second = items[2];\n if (first.Kind !== 'session' || second.Kind !== 'session') throw new Error('expected session blocks');\n expect(first.Group.SessionID).toBe('S-1');\n expect(second.Group.SessionID).toBe('S-2');\n });\n\n it('groups session ids case-insensitively (SQL Server uppercase vs PostgreSQL lowercase)', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'ABC-DEF' }),\n detail({ AgentSessionID: 'abc-def' })\n ]);\n\n expect(kinds(items)).toEqual(['session']);\n const block = items[0];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.DetailCount).toBe(2);\n });\n});\n\ndescribe('BuildConversationTimeline — turn counting mirrors review-mode visibility', () => {\n it('folds hidden rows (junction anchors) into the block WITHOUT counting them as turns', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'S-1', Role: 'User', Message: 'spoken turn' }),\n detail({ AgentSessionID: 'S-1', Role: 'AI', Message: 'artifact anchor', HiddenToUser: true })\n ]);\n\n const block = items[0];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.DetailCount).toBe(2);\n expect(block.Group.TurnCount).toBe(1);\n expect(block.Group.LastTurnPreview).toBe('spoken turn'); // hidden row never becomes the preview\n });\n\n it('excludes empty-text and Error-role rows from the turn count', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'S-1', Message: ' ' }),\n detail({ AgentSessionID: 'S-1', Role: 'Error', Message: 'boom' }),\n detail({ AgentSessionID: 'S-1', Role: 'AI', Message: 'real turn' })\n ]);\n\n const block = items[0];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.DetailCount).toBe(3);\n expect(block.Group.TurnCount).toBe(1);\n expect(block.Group.LastTurnRole).toBe('Assistant');\n });\n\n it('tolerates string timestamps and missing timestamps when computing the range', () => {\n const items = BuildConversationTimeline([\n detail({ AgentSessionID: 'S-1', __mj_CreatedAt: '2026-06-01T10:00:00Z' as unknown as Date }),\n detail({ AgentSessionID: 'S-1', __mj_CreatedAt: null }),\n detail({ AgentSessionID: 'S-1', __mj_CreatedAt: '2026-06-01T10:05:00Z' as unknown as Date })\n ]);\n\n const block = items[0];\n if (block.Kind !== 'session') throw new Error('expected a session block');\n expect(block.Group.StartedAt?.toISOString()).toBe('2026-06-01T10:00:00.000Z');\n expect(block.Group.EndedAt?.toISOString()).toBe('2026-06-01T10:05:00.000Z');\n });\n});\n"]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @fileoverview Tests for RealtimeSessionsAdapter — the Angular bridge from
3
+ * `RealtimeSessionService`'s native observables to the runtime's
4
+ * `ISessionsAdapter` contract.
5
+ *
6
+ * Covers:
7
+ * - session-started bridge (forwards sessionId + channelNames from the service).
8
+ * - session-channel diff: ActiveChannels$ array deltas produce open/closed
9
+ * events; the first non-empty emission produces opens for every channel.
10
+ * - session-channel guards against null CurrentAgentSessionId (the post-teardown
11
+ * window where ActiveChannels$ emits [] but agentSessionId is already null).
12
+ * - session-ended bridge (forwards sessionId + narrowed reason verbatim).
13
+ * - Dispose unsubscribes cleanly.
14
+ *
15
+ * Uses a hand-rolled RealtimeSessionService stub to avoid pulling the full Angular
16
+ * DI tree — we only need the observable surface and CurrentAgentSessionId getter.
17
+ */
18
+ export {};
19
+ //# sourceMappingURL=realtime-sessions-adapter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-sessions-adapter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-sessions-adapter.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * @fileoverview Tests for RealtimeSessionsAdapter — the Angular bridge from
3
+ * `RealtimeSessionService`'s native observables to the runtime's
4
+ * `ISessionsAdapter` contract.
5
+ *
6
+ * Covers:
7
+ * - session-started bridge (forwards sessionId + channelNames from the service).
8
+ * - session-channel diff: ActiveChannels$ array deltas produce open/closed
9
+ * events; the first non-empty emission produces opens for every channel.
10
+ * - session-channel guards against null CurrentAgentSessionId (the post-teardown
11
+ * window where ActiveChannels$ emits [] but agentSessionId is already null).
12
+ * - session-ended bridge (forwards sessionId + narrowed reason verbatim).
13
+ * - Dispose unsubscribes cleanly.
14
+ *
15
+ * Uses a hand-rolled RealtimeSessionService stub to avoid pulling the full Angular
16
+ * DI tree — we only need the observable surface and CurrentAgentSessionId getter.
17
+ */
18
+ import { describe, it, expect } from 'vitest';
19
+ import { BehaviorSubject, Subject } from 'rxjs';
20
+ import { RealtimeSessionsAdapter } from '../lib/services/realtime-sessions-adapter';
21
+ /** Minimal RealtimeSessionService stub. Only the observables + the one getter
22
+ * RealtimeSessionsAdapter consumes. Any other API isn't needed for these tests. */
23
+ function makeStubVoiceService() {
24
+ const sessionStarted$ = new Subject();
25
+ const sessionEnded$ = new Subject();
26
+ const activeChannels$ = new BehaviorSubject([]);
27
+ let currentAgentSessionId = null;
28
+ const service = {
29
+ SessionStarted$: sessionStarted$.asObservable(),
30
+ SessionEnded$: sessionEnded$.asObservable(),
31
+ ActiveChannels$: activeChannels$.asObservable(),
32
+ get CurrentAgentSessionId() {
33
+ return currentAgentSessionId;
34
+ },
35
+ };
36
+ return {
37
+ // Pass-through to the adapter via `as never` — we typecast away from the real
38
+ // RealtimeSessionService here because we only implement the surface the adapter
39
+ // reads. This is fine for test purposes.
40
+ service: service,
41
+ // Control surface for tests:
42
+ emitStarted: (e) => sessionStarted$.next(e),
43
+ emitEnded: (e) => sessionEnded$.next(e),
44
+ emitChannels: (channels) => activeChannels$.next(channels),
45
+ setSessionId: (id) => {
46
+ currentAgentSessionId = id;
47
+ },
48
+ };
49
+ }
50
+ describe('RealtimeSessionsAdapter', () => {
51
+ it('bridges SessionStarted$ to a session-started event PLUS synthetic open events per channel', () => {
52
+ // Synthetic opens close the asymmetry caused by RealtimeSessionService emitting to
53
+ // ActiveChannels$ BEFORE agentSessionId is set during StartVoiceSession.
54
+ const stub = makeStubVoiceService();
55
+ const adapter = new RealtimeSessionsAdapter(stub.service);
56
+ const events = [];
57
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
58
+ stub.emitStarted({ sessionId: 'sess-1', channelNames: ['voice', 'whiteboard'] });
59
+ expect(events).toEqual([
60
+ { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['voice', 'whiteboard'] },
61
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },
62
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },
63
+ ]);
64
+ });
65
+ it('SessionStarted$ with empty channelNames emits ONLY the started event (no synthetic opens)', () => {
66
+ const stub = makeStubVoiceService();
67
+ const adapter = new RealtimeSessionsAdapter(stub.service);
68
+ const events = [];
69
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
70
+ stub.emitStarted({ sessionId: 'sess-empty', channelNames: [] });
71
+ expect(events).toEqual([
72
+ { kind: 'session-started', sessionId: 'sess-empty', channelKinds: [] },
73
+ ]);
74
+ });
75
+ it('bridges SessionEnded$ to a session-ended event with reason preserved', () => {
76
+ const stub = makeStubVoiceService();
77
+ const adapter = new RealtimeSessionsAdapter(stub.service);
78
+ const events = [];
79
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
80
+ stub.emitEnded({ sessionId: 'sess-1', reason: 'explicit' });
81
+ stub.emitEnded({ sessionId: 'sess-2', reason: 'error' });
82
+ expect(events).toEqual([
83
+ { kind: 'session-ended', sessionId: 'sess-1', reason: 'explicit' },
84
+ { kind: 'session-ended', sessionId: 'sess-2', reason: 'error' },
85
+ ]);
86
+ });
87
+ it('mid-session: emits session-channel "open" for newly added channels (diff path)', () => {
88
+ // Simulates a channel plugging in DURING an already-started session.
89
+ // SessionStarted$ has already fired and set the initial channels;
90
+ // adding a new one mid-session goes through the ActiveChannels$ diff.
91
+ const stub = makeStubVoiceService();
92
+ stub.setSessionId('sess-1');
93
+ // Pre-condition: initial channel is already established (the synthetic-open
94
+ // from SessionStarted$ already happened in real life; here we just seed
95
+ // the diff's "prev" state by emitting the initial array).
96
+ const adapter = new RealtimeSessionsAdapter(stub.service);
97
+ const events = [];
98
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
99
+ stub.emitChannels([{ ChannelName: 'voice' }]); // initial — sessionId set, fires open
100
+ stub.emitChannels([{ ChannelName: 'voice' }, { ChannelName: 'whiteboard' }]); // add whiteboard — diff emits open
101
+ expect(events).toEqual([
102
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },
103
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },
104
+ ]);
105
+ });
106
+ it('mid-session: emits "open" for added channels and "closed" for removed channels', () => {
107
+ const stub = makeStubVoiceService();
108
+ stub.setSessionId('sess-1');
109
+ const adapter = new RealtimeSessionsAdapter(stub.service);
110
+ const events = [];
111
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
112
+ // First non-empty emission — opens.
113
+ stub.emitChannels([{ ChannelName: 'voice' }]);
114
+ // Add whiteboard.
115
+ stub.emitChannels([{ ChannelName: 'voice' }, { ChannelName: 'whiteboard' }]);
116
+ // Remove voice.
117
+ stub.emitChannels([{ ChannelName: 'whiteboard' }]);
118
+ expect(events).toEqual([
119
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },
120
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },
121
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'closed' },
122
+ ]);
123
+ });
124
+ it('real-world ordering: ActiveChannels$ during startChannels (sessionId null) skipped; SessionStarted$ then emits opens; teardown empties channels and emits closes', () => {
125
+ // This is the canonical real-world scenario the synthetic-open fix exists for.
126
+ const stub = makeStubVoiceService();
127
+ const adapter = new RealtimeSessionsAdapter(stub.service);
128
+ const events = [];
129
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
130
+ // 1. startChannels() runs first — ActiveChannels$ emits with sessionId still null.
131
+ // The diff handler must skip; nothing emitted yet.
132
+ stub.emitChannels([{ ChannelName: 'whiteboard' }]);
133
+ expect(events).toEqual([]);
134
+ // 2. mintSession() resolves, agentSessionId is set, Connect() finishes —
135
+ // SessionStarted$ fires. Synthetic open events come from there.
136
+ stub.setSessionId('sess-1');
137
+ stub.emitStarted({ sessionId: 'sess-1', channelNames: ['whiteboard'] });
138
+ expect(events).toEqual([
139
+ { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['whiteboard'] },
140
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },
141
+ ]);
142
+ // 3. Teardown: disposeChannels emits [] WHILE agentSessionId is still set.
143
+ // Diff handler emits close.
144
+ stub.emitChannels([]);
145
+ // 4. teardown nulls agentSessionId next, then emits SessionEnded$.
146
+ stub.setSessionId(null);
147
+ stub.emitEnded({ sessionId: 'sess-1', reason: 'explicit' });
148
+ expect(events).toEqual([
149
+ { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['whiteboard'] },
150
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },
151
+ { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'closed' },
152
+ { kind: 'session-ended', sessionId: 'sess-1', reason: 'explicit' },
153
+ ]);
154
+ });
155
+ it('skips channel events while CurrentAgentSessionId is null', () => {
156
+ const stub = makeStubVoiceService();
157
+ // No session id set — simulates the moment after teardown nulls agentSessionId
158
+ // but ActiveChannels$ is still about to emit [], OR the initial startChannels
159
+ // window before mintSession resolves.
160
+ const adapter = new RealtimeSessionsAdapter(stub.service);
161
+ const events = [];
162
+ adapter.SessionLifecycle$.subscribe((e) => events.push(e));
163
+ stub.emitChannels([{ ChannelName: 'voice' }]);
164
+ stub.emitChannels([]);
165
+ expect(events).toEqual([]);
166
+ });
167
+ it('Dispose unsubscribes from all bridged observables', () => {
168
+ const stub = makeStubVoiceService();
169
+ stub.setSessionId('sess-1');
170
+ const adapter = new RealtimeSessionsAdapter(stub.service);
171
+ const events = [];
172
+ const sub = adapter.SessionLifecycle$.subscribe((e) => events.push(e));
173
+ adapter.Dispose();
174
+ // After Dispose, none of these should produce events on the subscriber.
175
+ stub.emitStarted({ sessionId: 'sess-x', channelNames: [] });
176
+ stub.emitChannels([{ ChannelName: 'voice' }]);
177
+ stub.emitEnded({ sessionId: 'sess-x', reason: 'explicit' });
178
+ expect(events).toEqual([]);
179
+ sub.unsubscribe();
180
+ });
181
+ it('Dispose is idempotent', () => {
182
+ const stub = makeStubVoiceService();
183
+ const adapter = new RealtimeSessionsAdapter(stub.service);
184
+ expect(() => adapter.Dispose()).not.toThrow();
185
+ expect(() => adapter.Dispose()).not.toThrow();
186
+ });
187
+ });
188
+ //# sourceMappingURL=realtime-sessions-adapter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-sessions-adapter.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-sessions-adapter.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @fileoverview Tests for RealtimeSessionsAdapter — the Angular bridge from\n * `RealtimeSessionService`'s native observables to the runtime's\n * `ISessionsAdapter` contract.\n *\n * Covers:\n * - session-started bridge (forwards sessionId + channelNames from the service).\n * - session-channel diff: ActiveChannels$ array deltas produce open/closed\n * events; the first non-empty emission produces opens for every channel.\n * - session-channel guards against null CurrentAgentSessionId (the post-teardown\n * window where ActiveChannels$ emits [] but agentSessionId is already null).\n * - session-ended bridge (forwards sessionId + narrowed reason verbatim).\n * - Dispose unsubscribes cleanly.\n *\n * Uses a hand-rolled RealtimeSessionService stub to avoid pulling the full Angular\n * DI tree — we only need the observable surface and CurrentAgentSessionId getter.\n */\n\nimport { describe, it, expect, vi } from 'vitest';\nimport { BehaviorSubject, Subject } from 'rxjs';\nimport type { SessionLifecycleEvent } from '@memberjunction/conversations-runtime';\nimport { RealtimeSessionsAdapter } from '../lib/services/realtime-sessions-adapter';\n\n/** Minimal RealtimeSessionService stub. Only the observables + the one getter\n * RealtimeSessionsAdapter consumes. Any other API isn't needed for these tests. */\nfunction makeStubVoiceService() {\n const sessionStarted$ = new Subject<{ sessionId: string; channelNames: string[] }>();\n const sessionEnded$ = new Subject<{ sessionId: string; reason: 'explicit' | 'error' }>();\n const activeChannels$ = new BehaviorSubject<Array<{ ChannelName: string }>>([]);\n\n let currentAgentSessionId: string | null = null;\n\n const service = {\n SessionStarted$: sessionStarted$.asObservable(),\n SessionEnded$: sessionEnded$.asObservable(),\n ActiveChannels$: activeChannels$.asObservable(),\n get CurrentAgentSessionId() {\n return currentAgentSessionId;\n },\n };\n\n return {\n // Pass-through to the adapter via `as never` — we typecast away from the real\n // RealtimeSessionService here because we only implement the surface the adapter\n // reads. This is fine for test purposes.\n service: service as never,\n // Control surface for tests:\n emitStarted: (e: { sessionId: string; channelNames: string[] }) => sessionStarted$.next(e),\n emitEnded: (e: { sessionId: string; reason: 'explicit' | 'error' }) => sessionEnded$.next(e),\n emitChannels: (channels: Array<{ ChannelName: string }>) => activeChannels$.next(channels),\n setSessionId: (id: string | null) => {\n currentAgentSessionId = id;\n },\n };\n}\n\ndescribe('RealtimeSessionsAdapter', () => {\n it('bridges SessionStarted$ to a session-started event PLUS synthetic open events per channel', () => {\n // Synthetic opens close the asymmetry caused by RealtimeSessionService emitting to\n // ActiveChannels$ BEFORE agentSessionId is set during StartVoiceSession.\n const stub = makeStubVoiceService();\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n stub.emitStarted({ sessionId: 'sess-1', channelNames: ['voice', 'whiteboard'] });\n\n expect(events).toEqual([\n { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['voice', 'whiteboard'] },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },\n ]);\n });\n\n it('SessionStarted$ with empty channelNames emits ONLY the started event (no synthetic opens)', () => {\n const stub = makeStubVoiceService();\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n stub.emitStarted({ sessionId: 'sess-empty', channelNames: [] });\n\n expect(events).toEqual([\n { kind: 'session-started', sessionId: 'sess-empty', channelKinds: [] },\n ]);\n });\n\n it('bridges SessionEnded$ to a session-ended event with reason preserved', () => {\n const stub = makeStubVoiceService();\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n stub.emitEnded({ sessionId: 'sess-1', reason: 'explicit' });\n stub.emitEnded({ sessionId: 'sess-2', reason: 'error' });\n\n expect(events).toEqual([\n { kind: 'session-ended', sessionId: 'sess-1', reason: 'explicit' },\n { kind: 'session-ended', sessionId: 'sess-2', reason: 'error' },\n ]);\n });\n\n it('mid-session: emits session-channel \"open\" for newly added channels (diff path)', () => {\n // Simulates a channel plugging in DURING an already-started session.\n // SessionStarted$ has already fired and set the initial channels;\n // adding a new one mid-session goes through the ActiveChannels$ diff.\n const stub = makeStubVoiceService();\n stub.setSessionId('sess-1');\n // Pre-condition: initial channel is already established (the synthetic-open\n // from SessionStarted$ already happened in real life; here we just seed\n // the diff's \"prev\" state by emitting the initial array).\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n stub.emitChannels([{ ChannelName: 'voice' }]); // initial — sessionId set, fires open\n stub.emitChannels([{ ChannelName: 'voice' }, { ChannelName: 'whiteboard' }]); // add whiteboard — diff emits open\n\n expect(events).toEqual([\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },\n ]);\n });\n\n it('mid-session: emits \"open\" for added channels and \"closed\" for removed channels', () => {\n const stub = makeStubVoiceService();\n stub.setSessionId('sess-1');\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n // First non-empty emission — opens.\n stub.emitChannels([{ ChannelName: 'voice' }]);\n // Add whiteboard.\n stub.emitChannels([{ ChannelName: 'voice' }, { ChannelName: 'whiteboard' }]);\n // Remove voice.\n stub.emitChannels([{ ChannelName: 'whiteboard' }]);\n\n expect(events).toEqual([\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'open' },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'voice', state: 'closed' },\n ]);\n });\n\n it('real-world ordering: ActiveChannels$ during startChannels (sessionId null) skipped; SessionStarted$ then emits opens; teardown empties channels and emits closes', () => {\n // This is the canonical real-world scenario the synthetic-open fix exists for.\n const stub = makeStubVoiceService();\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n // 1. startChannels() runs first — ActiveChannels$ emits with sessionId still null.\n // The diff handler must skip; nothing emitted yet.\n stub.emitChannels([{ ChannelName: 'whiteboard' }]);\n expect(events).toEqual([]);\n\n // 2. mintSession() resolves, agentSessionId is set, Connect() finishes —\n // SessionStarted$ fires. Synthetic open events come from there.\n stub.setSessionId('sess-1');\n stub.emitStarted({ sessionId: 'sess-1', channelNames: ['whiteboard'] });\n\n expect(events).toEqual([\n { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['whiteboard'] },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },\n ]);\n\n // 3. Teardown: disposeChannels emits [] WHILE agentSessionId is still set.\n // Diff handler emits close.\n stub.emitChannels([]);\n // 4. teardown nulls agentSessionId next, then emits SessionEnded$.\n stub.setSessionId(null);\n stub.emitEnded({ sessionId: 'sess-1', reason: 'explicit' });\n\n expect(events).toEqual([\n { kind: 'session-started', sessionId: 'sess-1', channelKinds: ['whiteboard'] },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'open' },\n { kind: 'session-channel', sessionId: 'sess-1', channelKind: 'whiteboard', state: 'closed' },\n { kind: 'session-ended', sessionId: 'sess-1', reason: 'explicit' },\n ]);\n });\n\n it('skips channel events while CurrentAgentSessionId is null', () => {\n const stub = makeStubVoiceService();\n // No session id set — simulates the moment after teardown nulls agentSessionId\n // but ActiveChannels$ is still about to emit [], OR the initial startChannels\n // window before mintSession resolves.\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n stub.emitChannels([{ ChannelName: 'voice' }]);\n stub.emitChannels([]);\n\n expect(events).toEqual([]);\n });\n\n it('Dispose unsubscribes from all bridged observables', () => {\n const stub = makeStubVoiceService();\n stub.setSessionId('sess-1');\n const adapter = new RealtimeSessionsAdapter(stub.service);\n const events: SessionLifecycleEvent[] = [];\n const sub = adapter.SessionLifecycle$.subscribe((e) => events.push(e));\n\n adapter.Dispose();\n\n // After Dispose, none of these should produce events on the subscriber.\n stub.emitStarted({ sessionId: 'sess-x', channelNames: [] });\n stub.emitChannels([{ ChannelName: 'voice' }]);\n stub.emitEnded({ sessionId: 'sess-x', reason: 'explicit' });\n\n expect(events).toEqual([]);\n sub.unsubscribe();\n });\n\n it('Dispose is idempotent', () => {\n const stub = makeStubVoiceService();\n const adapter = new RealtimeSessionsAdapter(stub.service);\n\n expect(() => adapter.Dispose()).not.toThrow();\n expect(() => adapter.Dispose()).not.toThrow();\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=realtime-surface-panel-prefs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-surface-panel-prefs.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-surface-panel-prefs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Unit tests for the surface-panel width preference helpers — the framework-free
3
+ * clamp / parse / serialize rules behind the call overlay's drag-resizable panel
4
+ * (persisted per-user via UserInfoEngine under `mj.realtimeVoice.surfacePanel.v1`).
5
+ */
6
+ import { describe, it, expect } from 'vitest';
7
+ import { ClampSurfacePanelWidth, IsSurfacePanelDrag, ParseSurfacePanelPref, SerializeSurfacePanelPref, SurfacePanelDragWidth, SURFACE_PANEL_DRAG_CLICK_TOLERANCE, SURFACE_PANEL_MIN_WIDTH, SURFACE_PANEL_MAX_FRACTION, SURFACE_PANEL_PREF_KEY } from '../lib/components/realtime/realtime-surface-panel-prefs';
8
+ describe('ClampSurfacePanelWidth', () => {
9
+ it('passes through a width inside the [min, 70% of overlay] band', () => {
10
+ expect(ClampSurfacePanelWidth(500, 2000)).toBe(500);
11
+ });
12
+ it('clamps below the minimum up to the minimum', () => {
13
+ expect(ClampSurfacePanelWidth(100, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);
14
+ });
15
+ it('clamps above 70% of the overlay down to the cap', () => {
16
+ expect(ClampSurfacePanelWidth(1900, 2000)).toBe(2000 * SURFACE_PANEL_MAX_FRACTION);
17
+ });
18
+ it('enforces only the minimum when the overlay width is unknown', () => {
19
+ expect(ClampSurfacePanelWidth(5000, 0)).toBe(5000);
20
+ expect(ClampSurfacePanelWidth(100, NaN)).toBe(SURFACE_PANEL_MIN_WIDTH);
21
+ });
22
+ it('resolves a tiny overlay (cap below min) to the minimum', () => {
23
+ expect(ClampSurfacePanelWidth(400, 200)).toBe(SURFACE_PANEL_MIN_WIDTH);
24
+ });
25
+ it('returns the minimum for any non-finite candidate width (NaN, Infinity)', () => {
26
+ expect(ClampSurfacePanelWidth(NaN, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);
27
+ expect(ClampSurfacePanelWidth(Infinity, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);
28
+ });
29
+ });
30
+ describe('SurfacePanelDragWidth', () => {
31
+ it('grows the RIGHT-side panel when the pointer moves left', () => {
32
+ expect(SurfacePanelDragWidth(500, 1000, 900, 2000)).toBe(600);
33
+ });
34
+ it('shrinks the panel when the pointer moves right', () => {
35
+ expect(SurfacePanelDragWidth(500, 1000, 1100, 2000)).toBe(400);
36
+ });
37
+ it('returns the start width unchanged for a zero-delta gesture (bare click)', () => {
38
+ expect(SurfacePanelDragWidth(500, 1000, 1000, 2000)).toBe(500);
39
+ });
40
+ it('clamps the live width to the minimum', () => {
41
+ expect(SurfacePanelDragWidth(360, 1000, 1500, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);
42
+ });
43
+ it('clamps the live width to 70% of the overlay', () => {
44
+ expect(SurfacePanelDragWidth(500, 1000, 0, 2000)).toBe(2000 * SURFACE_PANEL_MAX_FRACTION);
45
+ });
46
+ it('enforces only the minimum when the overlay width is unknown', () => {
47
+ expect(SurfacePanelDragWidth(500, 1000, 0, 0)).toBe(1500);
48
+ });
49
+ });
50
+ describe('IsSurfacePanelDrag (click-vs-drag guard)', () => {
51
+ it('treats a zero-movement gesture as a CLICK (never adopt/persist)', () => {
52
+ expect(IsSurfacePanelDrag(1000, 1000)).toBe(false);
53
+ });
54
+ it('treats sub-tolerance jitter as a CLICK', () => {
55
+ expect(IsSurfacePanelDrag(1000, 1000 + SURFACE_PANEL_DRAG_CLICK_TOLERANCE - 1)).toBe(false);
56
+ expect(IsSurfacePanelDrag(1000, 1000 - (SURFACE_PANEL_DRAG_CLICK_TOLERANCE - 1))).toBe(false);
57
+ });
58
+ it('treats movement at or beyond the tolerance as a DRAG, in either direction', () => {
59
+ expect(IsSurfacePanelDrag(1000, 1000 + SURFACE_PANEL_DRAG_CLICK_TOLERANCE)).toBe(true);
60
+ expect(IsSurfacePanelDrag(1000, 1000 - SURFACE_PANEL_DRAG_CLICK_TOLERANCE)).toBe(true);
61
+ expect(IsSurfacePanelDrag(1000, 700)).toBe(true);
62
+ });
63
+ it('never reports a drag for degenerate (non-finite) coordinates', () => {
64
+ expect(IsSurfacePanelDrag(NaN, 1000)).toBe(false);
65
+ expect(IsSurfacePanelDrag(1000, NaN)).toBe(false);
66
+ expect(IsSurfacePanelDrag(Infinity, 0)).toBe(false);
67
+ });
68
+ });
69
+ describe('ParseSurfacePanelPref', () => {
70
+ it('parses a valid persisted width', () => {
71
+ expect(ParseSurfacePanelPref('{"width":512}')).toEqual({ Width: 512 });
72
+ });
73
+ it('round-trips through SerializeSurfacePanelPref', () => {
74
+ expect(ParseSurfacePanelPref(SerializeSurfacePanelPref(420))).toEqual({ Width: 420 });
75
+ });
76
+ it('treats a serialized RESET ({"width":null}) as no preference', () => {
77
+ expect(ParseSurfacePanelPref(SerializeSurfacePanelPref(null))).toBeNull();
78
+ });
79
+ it.each([
80
+ ['missing', undefined],
81
+ ['null', null],
82
+ ['blank', ''],
83
+ ['malformed JSON', '{width:'],
84
+ ['non-object', '"512"'],
85
+ ['JSON null', 'null'],
86
+ ['missing width', '{}'],
87
+ ['non-numeric width', '{"width":"big"}'],
88
+ ['non-finite width', '{"width":null}'],
89
+ ['zero width', '{"width":0}'],
90
+ ['negative width', '{"width":-50}']
91
+ ])('returns null (never throws) for %s', (_label, raw) => {
92
+ expect(ParseSurfacePanelPref(raw)).toBeNull();
93
+ });
94
+ });
95
+ describe('pref key', () => {
96
+ it('uses the versioned mj.* naming convention', () => {
97
+ expect(SURFACE_PANEL_PREF_KEY).toBe('mj.realtimeVoice.surfacePanel.v1');
98
+ });
99
+ });
100
+ //# sourceMappingURL=realtime-surface-panel-prefs.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-surface-panel-prefs.test.js","sourceRoot":"","sources":["../../src/__tests__/realtime-surface-panel-prefs.test.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Unit tests for the surface-panel width preference helpers — the framework-free\n * clamp / parse / serialize rules behind the call overlay's drag-resizable panel\n * (persisted per-user via UserInfoEngine under `mj.realtimeVoice.surfacePanel.v1`).\n */\nimport { describe, it, expect } from 'vitest';\nimport {\n ClampSurfacePanelWidth,\n IsSurfacePanelDrag,\n ParseSurfacePanelPref,\n SerializeSurfacePanelPref,\n SurfacePanelDragWidth,\n SURFACE_PANEL_DRAG_CLICK_TOLERANCE,\n SURFACE_PANEL_MIN_WIDTH,\n SURFACE_PANEL_MAX_FRACTION,\n SURFACE_PANEL_PREF_KEY\n} from '../lib/components/realtime/realtime-surface-panel-prefs';\n\ndescribe('ClampSurfacePanelWidth', () => {\n it('passes through a width inside the [min, 70% of overlay] band', () => {\n expect(ClampSurfacePanelWidth(500, 2000)).toBe(500);\n });\n\n it('clamps below the minimum up to the minimum', () => {\n expect(ClampSurfacePanelWidth(100, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);\n });\n\n it('clamps above 70% of the overlay down to the cap', () => {\n expect(ClampSurfacePanelWidth(1900, 2000)).toBe(2000 * SURFACE_PANEL_MAX_FRACTION);\n });\n\n it('enforces only the minimum when the overlay width is unknown', () => {\n expect(ClampSurfacePanelWidth(5000, 0)).toBe(5000);\n expect(ClampSurfacePanelWidth(100, NaN)).toBe(SURFACE_PANEL_MIN_WIDTH);\n });\n\n it('resolves a tiny overlay (cap below min) to the minimum', () => {\n expect(ClampSurfacePanelWidth(400, 200)).toBe(SURFACE_PANEL_MIN_WIDTH);\n });\n\n it('returns the minimum for any non-finite candidate width (NaN, Infinity)', () => {\n expect(ClampSurfacePanelWidth(NaN, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);\n expect(ClampSurfacePanelWidth(Infinity, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);\n });\n});\n\ndescribe('SurfacePanelDragWidth', () => {\n it('grows the RIGHT-side panel when the pointer moves left', () => {\n expect(SurfacePanelDragWidth(500, 1000, 900, 2000)).toBe(600);\n });\n\n it('shrinks the panel when the pointer moves right', () => {\n expect(SurfacePanelDragWidth(500, 1000, 1100, 2000)).toBe(400);\n });\n\n it('returns the start width unchanged for a zero-delta gesture (bare click)', () => {\n expect(SurfacePanelDragWidth(500, 1000, 1000, 2000)).toBe(500);\n });\n\n it('clamps the live width to the minimum', () => {\n expect(SurfacePanelDragWidth(360, 1000, 1500, 2000)).toBe(SURFACE_PANEL_MIN_WIDTH);\n });\n\n it('clamps the live width to 70% of the overlay', () => {\n expect(SurfacePanelDragWidth(500, 1000, 0, 2000)).toBe(2000 * SURFACE_PANEL_MAX_FRACTION);\n });\n\n it('enforces only the minimum when the overlay width is unknown', () => {\n expect(SurfacePanelDragWidth(500, 1000, 0, 0)).toBe(1500);\n });\n});\n\ndescribe('IsSurfacePanelDrag (click-vs-drag guard)', () => {\n it('treats a zero-movement gesture as a CLICK (never adopt/persist)', () => {\n expect(IsSurfacePanelDrag(1000, 1000)).toBe(false);\n });\n\n it('treats sub-tolerance jitter as a CLICK', () => {\n expect(IsSurfacePanelDrag(1000, 1000 + SURFACE_PANEL_DRAG_CLICK_TOLERANCE - 1)).toBe(false);\n expect(IsSurfacePanelDrag(1000, 1000 - (SURFACE_PANEL_DRAG_CLICK_TOLERANCE - 1))).toBe(false);\n });\n\n it('treats movement at or beyond the tolerance as a DRAG, in either direction', () => {\n expect(IsSurfacePanelDrag(1000, 1000 + SURFACE_PANEL_DRAG_CLICK_TOLERANCE)).toBe(true);\n expect(IsSurfacePanelDrag(1000, 1000 - SURFACE_PANEL_DRAG_CLICK_TOLERANCE)).toBe(true);\n expect(IsSurfacePanelDrag(1000, 700)).toBe(true);\n });\n\n it('never reports a drag for degenerate (non-finite) coordinates', () => {\n expect(IsSurfacePanelDrag(NaN, 1000)).toBe(false);\n expect(IsSurfacePanelDrag(1000, NaN)).toBe(false);\n expect(IsSurfacePanelDrag(Infinity, 0)).toBe(false);\n });\n});\n\ndescribe('ParseSurfacePanelPref', () => {\n it('parses a valid persisted width', () => {\n expect(ParseSurfacePanelPref('{\"width\":512}')).toEqual({ Width: 512 });\n });\n\n it('round-trips through SerializeSurfacePanelPref', () => {\n expect(ParseSurfacePanelPref(SerializeSurfacePanelPref(420))).toEqual({ Width: 420 });\n });\n\n it('treats a serialized RESET ({\"width\":null}) as no preference', () => {\n expect(ParseSurfacePanelPref(SerializeSurfacePanelPref(null))).toBeNull();\n });\n\n it.each([\n ['missing', undefined],\n ['null', null],\n ['blank', ''],\n ['malformed JSON', '{width:'],\n ['non-object', '\"512\"'],\n ['JSON null', 'null'],\n ['missing width', '{}'],\n ['non-numeric width', '{\"width\":\"big\"}'],\n ['non-finite width', '{\"width\":null}'],\n ['zero width', '{\"width\":0}'],\n ['negative width', '{\"width\":-50}']\n ])('returns null (never throws) for %s', (_label, raw) => {\n expect(ParseSurfacePanelPref(raw as string | null | undefined)).toBeNull();\n });\n});\n\ndescribe('pref key', () => {\n it('uses the versioned mj.* naming convention', () => {\n expect(SURFACE_PANEL_PREF_KEY).toBe('mj.realtimeVoice.surfacePanel.v1');\n });\n});\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=realtime-surface-tabs-model.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-surface-tabs-model.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/realtime-surface-tabs-model.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import { RealtimeSurfaceTabsModel, ShouldRemoveReviewWhiteboardTab } from '../lib/components/realtime/realtime-surface-tabs.model';
3
+ const weather = { ArtifactID: 'a-1', ArtifactVersionID: 'av-1', Name: 'Weather Report' };
4
+ const summary = { ArtifactID: 'a-2', ArtifactVersionID: 'av-2', Name: 'Q3 Summary' };
5
+ describe('RealtimeSurfaceTabsModel', () => {
6
+ let model;
7
+ beforeEach(() => {
8
+ model = new RealtimeSurfaceTabsModel();
9
+ });
10
+ it('starts with the Activity tab, focused', () => {
11
+ expect(model.Tabs).toHaveLength(1);
12
+ expect(model.Tabs[0]).toMatchObject({ Key: 'activity', Title: 'Activity', Kind: 'activity' });
13
+ expect(model.ActiveKey).toBe('activity');
14
+ expect(model.ActiveTab.Kind).toBe('activity');
15
+ expect(model.FlashKey).toBeNull();
16
+ });
17
+ describe('OpenArtifactTab', () => {
18
+ it('adds + flashes a new artifact tab WITHOUT stealing focus (glow, don\'t grab) and emits Changed$', () => {
19
+ const changed = vi.fn();
20
+ model.Changed$.subscribe(changed);
21
+ const tab = model.OpenArtifactTab(weather);
22
+ expect(model.Tabs).toHaveLength(2);
23
+ expect(tab).toMatchObject({
24
+ Key: 'artifact:av-1',
25
+ Title: 'Weather Report',
26
+ Icon: 'fa-solid fa-file-lines',
27
+ Kind: 'artifact'
28
+ });
29
+ expect(tab.Data?.Artifact).toEqual(weather);
30
+ expect(model.ActiveKey).toBe('activity'); // a finished artifact never steals the screen
31
+ expect(model.FlashKey).toBe('artifact:av-1');
32
+ expect(model.IsUnseen('artifact:av-1')).toBe(true); // persistent glow until visited
33
+ expect(changed).toHaveBeenCalledTimes(1);
34
+ });
35
+ it('can add WITH focus (the user-asked path) — focused tabs are never marked unseen', () => {
36
+ model.OpenArtifactTab(weather, true);
37
+ expect(model.ActiveKey).toBe('artifact:av-1');
38
+ expect(model.FlashKey).toBe('artifact:av-1');
39
+ expect(model.IsUnseen('artifact:av-1')).toBe(false);
40
+ });
41
+ it('history carryover (markUnseen=false) adds without glow or focus', () => {
42
+ model.OpenArtifactTab(weather, false, false);
43
+ expect(model.ActiveKey).toBe('activity');
44
+ expect(model.IsUnseen('artifact:av-1')).toBe(false);
45
+ });
46
+ it('visiting an unseen tab clears its glow', () => {
47
+ model.OpenArtifactTab(weather);
48
+ expect(model.IsUnseen('artifact:av-1')).toBe(true);
49
+ model.Focus('artifact:av-1');
50
+ expect(model.IsUnseen('artifact:av-1')).toBe(false);
51
+ });
52
+ it('dedupes by artifact version: re-opening with focus focuses the existing tab without re-flashing', () => {
53
+ model.OpenArtifactTab(weather, true);
54
+ model.ClearFlash();
55
+ model.Focus('activity');
56
+ const tab = model.OpenArtifactTab(weather, true);
57
+ expect(model.Tabs).toHaveLength(2);
58
+ expect(tab.Key).toBe('artifact:av-1');
59
+ expect(model.ActiveKey).toBe('artifact:av-1');
60
+ expect(model.FlashKey).toBeNull();
61
+ });
62
+ it('orders the strip channels… | artifacts (arrival order)… | Activity pinned LAST', () => {
63
+ model.RegisterChannelTab({ Key: 'whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
64
+ model.OpenArtifactTab(weather);
65
+ model.OpenArtifactTab(summary);
66
+ expect(model.Tabs.map(t => t.Key)).toEqual(['whiteboard', 'artifact:av-1', 'artifact:av-2', 'activity']);
67
+ });
68
+ it('replaces the Tabs array immutably', () => {
69
+ const before = model.Tabs;
70
+ model.OpenArtifactTab(weather);
71
+ expect(model.Tabs).not.toBe(before);
72
+ });
73
+ });
74
+ describe('RegisterChannelTab', () => {
75
+ it('adds a channel tab LEADING the strip (before Activity) without stealing focus by default', () => {
76
+ model.RegisterChannelTab({ Key: 'whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
77
+ expect(model.Tabs.map(t => t.Key)).toEqual(['whiteboard', 'activity']);
78
+ expect(model.Tabs[0].Kind).toBe('channel');
79
+ expect(model.Tabs[0].Data?.Content).toBeUndefined();
80
+ expect(model.ActiveKey).toBe('activity');
81
+ });
82
+ it('focuses when Focus is set', () => {
83
+ model.RegisterChannelTab({ Key: 'whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard', Focus: true });
84
+ expect(model.ActiveKey).toBe('whiteboard');
85
+ });
86
+ it('re-registering the same key updates the tab in place (placeholder → real surface)', () => {
87
+ model.RegisterChannelTab({ Key: 'whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
88
+ model.RegisterChannelTab({ Key: 'whiteboard', Title: 'Whiteboard · live', Icon: 'fa-solid fa-chalkboard' });
89
+ expect(model.Tabs).toHaveLength(2);
90
+ expect(model.Tabs[0].Title).toBe('Whiteboard · live');
91
+ });
92
+ });
93
+ describe('Focus / ClearFlash', () => {
94
+ it('Focus switches the active tab and emits', () => {
95
+ model.OpenArtifactTab(weather, false);
96
+ const changed = vi.fn();
97
+ model.Changed$.subscribe(changed);
98
+ model.Focus('artifact:av-1');
99
+ expect(model.ActiveKey).toBe('artifact:av-1');
100
+ expect(changed).toHaveBeenCalledTimes(1);
101
+ });
102
+ it('Focus is a no-op for unknown keys and the already-active key', () => {
103
+ const changed = vi.fn();
104
+ model.Changed$.subscribe(changed);
105
+ model.Focus('nope');
106
+ model.Focus('activity');
107
+ expect(model.ActiveKey).toBe('activity');
108
+ expect(changed).not.toHaveBeenCalled();
109
+ });
110
+ it('ClearFlash clears and emits once; subsequent calls are no-ops', () => {
111
+ model.OpenArtifactTab(weather);
112
+ const changed = vi.fn();
113
+ model.Changed$.subscribe(changed);
114
+ model.ClearFlash();
115
+ model.ClearFlash();
116
+ expect(model.FlashKey).toBeNull();
117
+ expect(changed).toHaveBeenCalledTimes(1);
118
+ });
119
+ it('ActiveTab falls back to the Activity tab for a stale ActiveKey', () => {
120
+ model.ActiveKey = 'gone';
121
+ expect(model.ActiveTab.Key).toBe('activity');
122
+ });
123
+ });
124
+ describe('RemoveTab', () => {
125
+ it('removes a channel tab and emits Changed$ once', () => {
126
+ model.RegisterChannelTab({ Key: 'Whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
127
+ const changed = vi.fn();
128
+ model.Changed$.subscribe(changed);
129
+ expect(model.RemoveTab('Whiteboard')).toBe(true);
130
+ expect(model.Tabs.some(t => t.Key === 'Whiteboard')).toBe(false);
131
+ expect(model.Tabs).toHaveLength(1); // Activity remains
132
+ expect(changed).toHaveBeenCalledTimes(1);
133
+ });
134
+ it('falls focus back to Activity when the removed tab was active', () => {
135
+ model.RegisterChannelTab({ Key: 'Whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard', Focus: true });
136
+ expect(model.ActiveKey).toBe('Whiteboard');
137
+ model.RemoveTab('Whiteboard');
138
+ expect(model.ActiveKey).toBe('activity');
139
+ expect(model.ActiveTab.Kind).toBe('activity');
140
+ });
141
+ it('keeps the current focus when a NON-active tab is removed', () => {
142
+ model.OpenArtifactTab(weather, true); // focused (user-asked path)
143
+ model.RegisterChannelTab({ Key: 'Whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
144
+ model.RemoveTab('Whiteboard');
145
+ expect(model.ActiveKey).toBe('artifact:av-1');
146
+ });
147
+ it('cleans a pending flash on the removed tab', () => {
148
+ model.OpenArtifactTab(weather); // flashes artifact:av-1
149
+ expect(model.FlashKey).toBe('artifact:av-1');
150
+ model.RemoveTab('artifact:av-1');
151
+ expect(model.FlashKey).toBeNull();
152
+ });
153
+ it('the Activity tab is IRREMOVABLE (no-op, no emission)', () => {
154
+ const changed = vi.fn();
155
+ model.Changed$.subscribe(changed);
156
+ expect(model.RemoveTab('activity')).toBe(false);
157
+ expect(model.Tabs).toHaveLength(1);
158
+ expect(changed).not.toHaveBeenCalled();
159
+ });
160
+ it('an unknown key is a no-op (false, no emission)', () => {
161
+ const changed = vi.fn();
162
+ model.Changed$.subscribe(changed);
163
+ expect(model.RemoveTab('nope')).toBe(false);
164
+ expect(changed).not.toHaveBeenCalled();
165
+ });
166
+ it('a removed channel tab can be re-registered cleanly afterward', () => {
167
+ model.RegisterChannelTab({ Key: 'Whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard' });
168
+ model.RemoveTab('Whiteboard');
169
+ model.RegisterChannelTab({ Key: 'Whiteboard', Title: 'Whiteboard', Icon: 'fa-solid fa-chalkboard', Focus: true });
170
+ expect(model.Tabs.some(t => t.Key === 'Whiteboard')).toBe(true);
171
+ expect(model.ActiveKey).toBe('Whiteboard');
172
+ });
173
+ });
174
+ });
175
+ describe('ShouldRemoveReviewWhiteboardTab (review→live continuation edge)', () => {
176
+ const whiteboard = { ChannelName: 'Whiteboard' };
177
+ const notes = { ChannelName: 'Notes' };
178
+ it('removes when live is active, a review board tab exists, and the live set lacks a Whiteboard channel', () => {
179
+ expect(ShouldRemoveReviewWhiteboardTab(true, true, [notes])).toBe(true);
180
+ expect(ShouldRemoveReviewWhiteboardTab(true, true, [])).toBe(true);
181
+ });
182
+ it('keeps the tab when the live channel set HAS a Whiteboard channel (it upgrades in place)', () => {
183
+ expect(ShouldRemoveReviewWhiteboardTab(true, true, [whiteboard])).toBe(false);
184
+ expect(ShouldRemoveReviewWhiteboardTab(true, true, [notes, { ChannelName: ' whiteboard ' }])).toBe(false);
185
+ });
186
+ it('never removes when no live session is active (initial / teardown [] emissions)', () => {
187
+ expect(ShouldRemoveReviewWhiteboardTab(false, true, [])).toBe(false);
188
+ });
189
+ it('never removes when review mode registered no whiteboard tab', () => {
190
+ expect(ShouldRemoveReviewWhiteboardTab(true, false, [])).toBe(false);
191
+ });
192
+ });
193
+ //# sourceMappingURL=realtime-surface-tabs-model.test.js.map