@memberjunction/ng-conversations 5.43.0 → 5.45.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 (405) hide show
  1. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts +0 -1
  2. package/dist/lib/components/active-tasks/active-tasks-panel.component.d.ts.map +1 -1
  3. package/dist/lib/components/active-tasks/active-tasks-panel.component.js +56 -52
  4. package/dist/lib/components/active-tasks/active-tasks-panel.component.js.map +1 -1
  5. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts +3 -1
  6. package/dist/lib/components/artifact/artifact-share-modal.component.d.ts.map +1 -1
  7. package/dist/lib/components/artifact/artifact-share-modal.component.js +52 -52
  8. package/dist/lib/components/artifact/artifact-share-modal.component.js.map +1 -1
  9. package/dist/lib/components/collection/collection-form-modal.component.d.ts.map +1 -1
  10. package/dist/lib/components/collection/collection-form-modal.component.js +10 -14
  11. package/dist/lib/components/collection/collection-form-modal.component.js.map +1 -1
  12. package/dist/lib/components/collection/collection-share-modal.component.d.ts +3 -1
  13. package/dist/lib/components/collection/collection-share-modal.component.d.ts.map +1 -1
  14. package/dist/lib/components/collection/collection-share-modal.component.js +56 -56
  15. package/dist/lib/components/collection/collection-share-modal.component.js.map +1 -1
  16. package/dist/lib/components/collection/collection-tree.component.d.ts +3 -1
  17. package/dist/lib/components/collection/collection-tree.component.d.ts.map +1 -1
  18. package/dist/lib/components/collection/collection-tree.component.js +10 -7
  19. package/dist/lib/components/collection/collection-tree.component.js.map +1 -1
  20. package/dist/lib/components/collection/collection-view.component.d.ts +3 -1
  21. package/dist/lib/components/collection/collection-view.component.d.ts.map +1 -1
  22. package/dist/lib/components/collection/collection-view.component.js +29 -41
  23. package/dist/lib/components/collection/collection-view.component.js.map +1 -1
  24. package/dist/lib/components/collection/collections-full-view.component.d.ts +4 -0
  25. package/dist/lib/components/collection/collections-full-view.component.d.ts.map +1 -1
  26. package/dist/lib/components/collection/collections-full-view.component.js +134 -166
  27. package/dist/lib/components/collection/collections-full-view.component.js.map +1 -1
  28. package/dist/lib/components/composer/ai-composer.component.d.ts +103 -0
  29. package/dist/lib/components/composer/ai-composer.component.d.ts.map +1 -0
  30. package/dist/lib/components/composer/ai-composer.component.js +263 -0
  31. package/dist/lib/components/composer/ai-composer.component.js.map +1 -0
  32. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts +76 -12
  33. package/dist/lib/components/conversation/conversation-chat-area.component.d.ts.map +1 -1
  34. package/dist/lib/components/conversation/conversation-chat-area.component.js +522 -158
  35. package/dist/lib/components/conversation/conversation-chat-area.component.js.map +1 -1
  36. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts +1 -1
  37. package/dist/lib/components/conversation/conversation-empty-state.component.d.ts.map +1 -1
  38. package/dist/lib/components/conversation/conversation-empty-state.component.js.map +1 -1
  39. package/dist/lib/components/conversation/pinned-messages-panel.component.js +6 -9
  40. package/dist/lib/components/conversation/pinned-messages-panel.component.js.map +1 -1
  41. package/dist/lib/components/library/library-full-view.component.d.ts.map +1 -1
  42. package/dist/lib/components/library/library-full-view.component.js +20 -24
  43. package/dist/lib/components/library/library-full-view.component.js.map +1 -1
  44. package/dist/lib/components/members/members-modal.component.d.ts.map +1 -1
  45. package/dist/lib/components/members/members-modal.component.js +10 -11
  46. package/dist/lib/components/members/members-modal.component.js.map +1 -1
  47. package/dist/lib/components/message/message-input.component.d.ts +87 -23
  48. package/dist/lib/components/message/message-input.component.d.ts.map +1 -1
  49. package/dist/lib/components/message/message-input.component.js +245 -158
  50. package/dist/lib/components/message/message-input.component.js.map +1 -1
  51. package/dist/lib/components/message/message-item.component.d.ts +11 -3
  52. package/dist/lib/components/message/message-item.component.d.ts.map +1 -1
  53. package/dist/lib/components/message/message-item.component.js +73 -21
  54. package/dist/lib/components/message/message-item.component.js.map +1 -1
  55. package/dist/lib/components/message/message-list.component.js +6 -9
  56. package/dist/lib/components/message/message-list.component.js.map +1 -1
  57. package/dist/lib/components/overlay/chat-overlay.component.d.ts +4 -1
  58. package/dist/lib/components/overlay/chat-overlay.component.d.ts.map +1 -1
  59. package/dist/lib/components/overlay/chat-overlay.component.js +9 -4
  60. package/dist/lib/components/overlay/chat-overlay.component.js.map +1 -1
  61. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts +45 -0
  62. package/dist/lib/components/realtime/channels/base-realtime-channel-client.d.ts.map +1 -1
  63. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js +10 -0
  64. package/dist/lib/components/realtime/channels/base-realtime-channel-client.js.map +1 -1
  65. package/dist/lib/components/realtime/channels/client-context-channel.d.ts +46 -0
  66. package/dist/lib/components/realtime/channels/client-context-channel.d.ts.map +1 -0
  67. package/dist/lib/components/realtime/channels/client-context-channel.js +174 -0
  68. package/dist/lib/components/realtime/channels/client-context-channel.js.map +1 -0
  69. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts +123 -0
  70. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.d.ts.map +1 -0
  71. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js +397 -0
  72. package/dist/lib/components/realtime/evidence-playback/realtime-evidence-playback.component.js.map +1 -0
  73. package/dist/lib/components/realtime/media/media-channel-state.d.ts +103 -0
  74. package/dist/lib/components/realtime/media/media-channel-state.d.ts.map +1 -0
  75. package/dist/lib/components/realtime/media/media-channel-state.js +143 -0
  76. package/dist/lib/components/realtime/media/media-channel-state.js.map +1 -0
  77. package/dist/lib/components/realtime/media/media-channel-tools.d.ts +24 -0
  78. package/dist/lib/components/realtime/media/media-channel-tools.d.ts.map +1 -0
  79. package/dist/lib/components/realtime/media/media-channel-tools.js +107 -0
  80. package/dist/lib/components/realtime/media/media-channel-tools.js.map +1 -0
  81. package/dist/lib/components/realtime/media/media-channel.d.ts +89 -0
  82. package/dist/lib/components/realtime/media/media-channel.d.ts.map +1 -0
  83. package/dist/lib/components/realtime/media/media-channel.js +253 -0
  84. package/dist/lib/components/realtime/media/media-channel.js.map +1 -0
  85. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts +141 -0
  86. package/dist/lib/components/realtime/media/realtime-media-surface.component.d.ts.map +1 -0
  87. package/dist/lib/components/realtime/media/realtime-media-surface.component.js +574 -0
  88. package/dist/lib/components/realtime/media/realtime-media-surface.component.js.map +1 -0
  89. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts +59 -14
  90. package/dist/lib/components/realtime/realtime-activity-rail.component.d.ts.map +1 -1
  91. package/dist/lib/components/realtime/realtime-activity-rail.component.js +277 -74
  92. package/dist/lib/components/realtime/realtime-activity-rail.component.js.map +1 -1
  93. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts +28 -27
  94. package/dist/lib/components/realtime/realtime-agent-banner.component.d.ts.map +1 -1
  95. package/dist/lib/components/realtime/realtime-agent-banner.component.js +144 -143
  96. package/dist/lib/components/realtime/realtime-agent-banner.component.js.map +1 -1
  97. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts +12 -0
  98. package/dist/lib/components/realtime/realtime-agent-picker.component.d.ts.map +1 -1
  99. package/dist/lib/components/realtime/realtime-agent-picker.component.js +105 -43
  100. package/dist/lib/components/realtime/realtime-agent-picker.component.js.map +1 -1
  101. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts +2 -2
  102. package/dist/lib/components/realtime/realtime-audio-visuals.d.ts.map +1 -1
  103. package/dist/lib/components/realtime/realtime-audio-visuals.js.map +1 -1
  104. package/dist/lib/components/realtime/realtime-composer.component.d.ts +41 -7
  105. package/dist/lib/components/realtime/realtime-composer.component.d.ts.map +1 -1
  106. package/dist/lib/components/realtime/realtime-composer.component.js +216 -102
  107. package/dist/lib/components/realtime/realtime-composer.component.js.map +1 -1
  108. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts +0 -2
  109. package/dist/lib/components/realtime/realtime-delegation-card.component.d.ts.map +1 -1
  110. package/dist/lib/components/realtime/realtime-delegation-card.component.js +67 -71
  111. package/dist/lib/components/realtime/realtime-delegation-card.component.js.map +1 -1
  112. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts +364 -44
  113. package/dist/lib/components/realtime/realtime-session-overlay.component.d.ts.map +1 -1
  114. package/dist/lib/components/realtime/realtime-session-overlay.component.js +839 -153
  115. package/dist/lib/components/realtime/realtime-session-overlay.component.js.map +1 -1
  116. package/dist/lib/components/realtime/realtime-session-state.d.ts +5 -5
  117. package/dist/lib/components/realtime/realtime-session-state.d.ts.map +1 -1
  118. package/dist/lib/components/realtime/realtime-session-state.js.map +1 -1
  119. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts +70 -0
  120. package/dist/lib/components/realtime/realtime-surface-tab-style.d.ts.map +1 -0
  121. package/dist/lib/components/realtime/realtime-surface-tab-style.js +115 -0
  122. package/dist/lib/components/realtime/realtime-surface-tab-style.js.map +1 -0
  123. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts +55 -57
  124. package/dist/lib/components/realtime/realtime-surface-tabs.component.d.ts.map +1 -1
  125. package/dist/lib/components/realtime/realtime-surface-tabs.component.js +165 -143
  126. package/dist/lib/components/realtime/realtime-surface-tabs.component.js.map +1 -1
  127. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts +80 -71
  128. package/dist/lib/components/realtime/realtime-surface-tabs.model.d.ts.map +1 -1
  129. package/dist/lib/components/realtime/realtime-surface-tabs.model.js +83 -113
  130. package/dist/lib/components/realtime/realtime-surface-tabs.model.js.map +1 -1
  131. package/dist/lib/components/realtime/realtime-ui-config.d.ts +270 -0
  132. package/dist/lib/components/realtime/realtime-ui-config.d.ts.map +1 -0
  133. package/dist/lib/components/realtime/realtime-ui-config.js +212 -0
  134. package/dist/lib/components/realtime/realtime-ui-config.js.map +1 -0
  135. package/dist/lib/components/search/search-panel.component.d.ts +2 -0
  136. package/dist/lib/components/search/search-panel.component.d.ts.map +1 -1
  137. package/dist/lib/components/search/search-panel.component.js +92 -101
  138. package/dist/lib/components/search/search-panel.component.js.map +1 -1
  139. package/dist/lib/components/share/share-modal.component.d.ts.map +1 -1
  140. package/dist/lib/components/share/share-modal.component.js +10 -11
  141. package/dist/lib/components/share/share-modal.component.js.map +1 -1
  142. package/dist/lib/components/shared/user-picker.component.d.ts.map +1 -1
  143. package/dist/lib/components/shared/user-picker.component.js +12 -15
  144. package/dist/lib/components/shared/user-picker.component.js.map +1 -1
  145. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts +11 -3
  146. package/dist/lib/components/sidebar/conversation-sidebar.component.d.ts.map +1 -1
  147. package/dist/lib/components/sidebar/conversation-sidebar.component.js +34 -10
  148. package/dist/lib/components/sidebar/conversation-sidebar.component.js.map +1 -1
  149. package/dist/lib/components/sidebar/routines-section.component.d.ts +73 -0
  150. package/dist/lib/components/sidebar/routines-section.component.d.ts.map +1 -0
  151. package/dist/lib/components/sidebar/routines-section.component.js +211 -0
  152. package/dist/lib/components/sidebar/routines-section.component.js.map +1 -0
  153. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts +12 -0
  154. package/dist/lib/components/tasks/tasks-dropdown.component.d.ts.map +1 -1
  155. package/dist/lib/components/tasks/tasks-dropdown.component.js +37 -1
  156. package/dist/lib/components/tasks/tasks-dropdown.component.js.map +1 -1
  157. package/dist/lib/components/workspace/conversation-workspace.component.d.ts +16 -5
  158. package/dist/lib/components/workspace/conversation-workspace.component.d.ts.map +1 -1
  159. package/dist/lib/components/workspace/conversation-workspace.component.js +51 -31
  160. package/dist/lib/components/workspace/conversation-workspace.component.js.map +1 -1
  161. package/dist/lib/composer-plugins/agent-mention.provider.d.ts +16 -0
  162. package/dist/lib/composer-plugins/agent-mention.provider.d.ts.map +1 -0
  163. package/dist/lib/composer-plugins/agent-mention.provider.js +28 -0
  164. package/dist/lib/composer-plugins/agent-mention.provider.js.map +1 -0
  165. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts +20 -0
  166. package/dist/lib/composer-plugins/base-conversation-mention.provider.d.ts.map +1 -0
  167. package/dist/lib/composer-plugins/base-conversation-mention.provider.js +32 -0
  168. package/dist/lib/composer-plugins/base-conversation-mention.provider.js.map +1 -0
  169. package/dist/lib/composer-plugins/load-composer-plugins.d.ts +9 -0
  170. package/dist/lib/composer-plugins/load-composer-plugins.d.ts.map +1 -0
  171. package/dist/lib/composer-plugins/load-composer-plugins.js +17 -0
  172. package/dist/lib/composer-plugins/load-composer-plugins.js.map +1 -0
  173. package/dist/lib/composer-plugins/record-mention.provider.d.ts +14 -0
  174. package/dist/lib/composer-plugins/record-mention.provider.d.ts.map +1 -0
  175. package/dist/lib/composer-plugins/record-mention.provider.js +26 -0
  176. package/dist/lib/composer-plugins/record-mention.provider.js.map +1 -0
  177. package/dist/lib/composer-plugins/skill-command.provider.d.ts +15 -0
  178. package/dist/lib/composer-plugins/skill-command.provider.d.ts.map +1 -0
  179. package/dist/lib/composer-plugins/skill-command.provider.js +27 -0
  180. package/dist/lib/composer-plugins/skill-command.provider.js.map +1 -0
  181. package/dist/lib/conversations.module.d.ts +68 -64
  182. package/dist/lib/conversations.module.d.ts.map +1 -1
  183. package/dist/lib/conversations.module.js +69 -18
  184. package/dist/lib/conversations.module.js.map +1 -1
  185. package/dist/lib/models/conversation-state.model.d.ts +6 -3
  186. package/dist/lib/models/conversation-state.model.d.ts.map +1 -1
  187. package/dist/lib/models/conversation-state.model.js.map +1 -1
  188. package/dist/lib/services/conversation-agent.service.d.ts +9 -2
  189. package/dist/lib/services/conversation-agent.service.d.ts.map +1 -1
  190. package/dist/lib/services/conversation-agent.service.js +26 -2
  191. package/dist/lib/services/conversation-agent.service.js.map +1 -1
  192. package/dist/lib/services/conversation-attachment.service.d.ts +1 -1
  193. package/dist/lib/services/conversation-attachment.service.d.ts.map +1 -1
  194. package/dist/lib/services/conversation-attachment.service.js.map +1 -1
  195. package/dist/lib/services/mention-autocomplete.service.d.ts +42 -19
  196. package/dist/lib/services/mention-autocomplete.service.d.ts.map +1 -1
  197. package/dist/lib/services/mention-autocomplete.service.js +113 -18
  198. package/dist/lib/services/mention-autocomplete.service.js.map +1 -1
  199. package/dist/lib/services/realtime-audio-recorder.d.ts +143 -0
  200. package/dist/lib/services/realtime-audio-recorder.d.ts.map +1 -0
  201. package/dist/lib/services/realtime-audio-recorder.js +379 -0
  202. package/dist/lib/services/realtime-audio-recorder.js.map +1 -0
  203. package/dist/lib/services/realtime-pcm-wav.d.ts +75 -0
  204. package/dist/lib/services/realtime-pcm-wav.d.ts.map +1 -0
  205. package/dist/lib/services/realtime-pcm-wav.js +205 -0
  206. package/dist/lib/services/realtime-pcm-wav.js.map +1 -0
  207. package/dist/lib/services/realtime-session-review.service.d.ts +28 -1
  208. package/dist/lib/services/realtime-session-review.service.d.ts.map +1 -1
  209. package/dist/lib/services/realtime-session-review.service.js +12 -3
  210. package/dist/lib/services/realtime-session-review.service.js.map +1 -1
  211. package/dist/lib/services/realtime-session.service.d.ts +210 -20
  212. package/dist/lib/services/realtime-session.service.d.ts.map +1 -1
  213. package/dist/lib/services/realtime-session.service.js +452 -21
  214. package/dist/lib/services/realtime-session.service.js.map +1 -1
  215. package/dist/lib/services/realtime-sessions-adapter.js +1 -1
  216. package/dist/lib/services/realtime-sessions-adapter.js.map +1 -1
  217. package/dist/lib/services/ui-command-handler.service.d.ts +7 -2
  218. package/dist/lib/services/ui-command-handler.service.d.ts.map +1 -1
  219. package/dist/lib/services/ui-command-handler.service.js +6 -2
  220. package/dist/lib/services/ui-command-handler.service.js.map +1 -1
  221. package/dist/lib/utils/plan-mode-preference.d.ts +62 -0
  222. package/dist/lib/utils/plan-mode-preference.d.ts.map +1 -0
  223. package/dist/lib/utils/plan-mode-preference.js +130 -0
  224. package/dist/lib/utils/plan-mode-preference.js.map +1 -0
  225. package/dist/public-api.d.ts +29 -3
  226. package/dist/public-api.d.ts.map +1 -1
  227. package/dist/public-api.js +46 -3
  228. package/dist/public-api.js.map +1 -1
  229. package/package.json +30 -26
  230. package/dist/__tests__/attachment-badge.test.d.ts +0 -2
  231. package/dist/__tests__/attachment-badge.test.d.ts.map +0 -1
  232. package/dist/__tests__/attachment-badge.test.js +0 -66
  233. package/dist/__tests__/attachment-badge.test.js.map +0 -1
  234. package/dist/__tests__/channel-optional-surface.test.d.ts +0 -2
  235. package/dist/__tests__/channel-optional-surface.test.d.ts.map +0 -1
  236. package/dist/__tests__/channel-optional-surface.test.js +0 -53
  237. package/dist/__tests__/channel-optional-surface.test.js.map +0 -1
  238. package/dist/__tests__/chat-events.test.d.ts +0 -14
  239. package/dist/__tests__/chat-events.test.d.ts.map +0 -1
  240. package/dist/__tests__/chat-events.test.js +0 -109
  241. package/dist/__tests__/chat-events.test.js.map +0 -1
  242. package/dist/__tests__/collections-full-view.test.d.ts +0 -2
  243. package/dist/__tests__/collections-full-view.test.d.ts.map +0 -1
  244. package/dist/__tests__/collections-full-view.test.js +0 -225
  245. package/dist/__tests__/collections-full-view.test.js.map +0 -1
  246. package/dist/__tests__/conversation-cache.test.d.ts +0 -2
  247. package/dist/__tests__/conversation-cache.test.d.ts.map +0 -1
  248. package/dist/__tests__/conversation-cache.test.js +0 -233
  249. package/dist/__tests__/conversation-cache.test.js.map +0 -1
  250. package/dist/__tests__/conversation-naming.test.d.ts +0 -2
  251. package/dist/__tests__/conversation-naming.test.d.ts.map +0 -1
  252. package/dist/__tests__/conversation-naming.test.js +0 -110
  253. package/dist/__tests__/conversation-naming.test.js.map +0 -1
  254. package/dist/__tests__/delegation-result-parser.test.d.ts +0 -2
  255. package/dist/__tests__/delegation-result-parser.test.d.ts.map +0 -1
  256. package/dist/__tests__/delegation-result-parser.test.js +0 -107
  257. package/dist/__tests__/delegation-result-parser.test.js.map +0 -1
  258. package/dist/__tests__/distinct-artifacts.test.d.ts +0 -2
  259. package/dist/__tests__/distinct-artifacts.test.d.ts.map +0 -1
  260. package/dist/__tests__/distinct-artifacts.test.js +0 -56
  261. package/dist/__tests__/distinct-artifacts.test.js.map +0 -1
  262. package/dist/__tests__/event-wiring.test.d.ts +0 -15
  263. package/dist/__tests__/event-wiring.test.d.ts.map +0 -1
  264. package/dist/__tests__/event-wiring.test.js +0 -100
  265. package/dist/__tests__/event-wiring.test.js.map +0 -1
  266. package/dist/__tests__/index.test.d.ts +0 -2
  267. package/dist/__tests__/index.test.d.ts.map +0 -1
  268. package/dist/__tests__/index.test.js +0 -7
  269. package/dist/__tests__/index.test.js.map +0 -1
  270. package/dist/__tests__/narration-template.test.d.ts +0 -2
  271. package/dist/__tests__/narration-template.test.d.ts.map +0 -1
  272. package/dist/__tests__/narration-template.test.js +0 -76
  273. package/dist/__tests__/narration-template.test.js.map +0 -1
  274. package/dist/__tests__/notification-model.test.d.ts +0 -2
  275. package/dist/__tests__/notification-model.test.d.ts.map +0 -1
  276. package/dist/__tests__/notification-model.test.js +0 -68
  277. package/dist/__tests__/notification-model.test.js.map +0 -1
  278. package/dist/__tests__/realtime-agent-picker-models.test.d.ts +0 -2
  279. package/dist/__tests__/realtime-agent-picker-models.test.d.ts.map +0 -1
  280. package/dist/__tests__/realtime-agent-picker-models.test.js +0 -49
  281. package/dist/__tests__/realtime-agent-picker-models.test.js.map +0 -1
  282. package/dist/__tests__/realtime-audio-visuals.test.d.ts +0 -2
  283. package/dist/__tests__/realtime-audio-visuals.test.d.ts.map +0 -1
  284. package/dist/__tests__/realtime-audio-visuals.test.js +0 -123
  285. package/dist/__tests__/realtime-audio-visuals.test.js.map +0 -1
  286. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts +0 -2
  287. package/dist/__tests__/realtime-delegation-card-cancel.test.d.ts.map +0 -1
  288. package/dist/__tests__/realtime-delegation-card-cancel.test.js +0 -48
  289. package/dist/__tests__/realtime-delegation-card-cancel.test.js.map +0 -1
  290. package/dist/__tests__/realtime-disclosure.test.d.ts +0 -2
  291. package/dist/__tests__/realtime-disclosure.test.d.ts.map +0 -1
  292. package/dist/__tests__/realtime-disclosure.test.js +0 -164
  293. package/dist/__tests__/realtime-disclosure.test.js.map +0 -1
  294. package/dist/__tests__/realtime-pairing.test.d.ts +0 -2
  295. package/dist/__tests__/realtime-pairing.test.d.ts.map +0 -1
  296. package/dist/__tests__/realtime-pairing.test.js +0 -207
  297. package/dist/__tests__/realtime-pairing.test.js.map +0 -1
  298. package/dist/__tests__/realtime-review-lifecycle.test.d.ts +0 -2
  299. package/dist/__tests__/realtime-review-lifecycle.test.d.ts.map +0 -1
  300. package/dist/__tests__/realtime-review-lifecycle.test.js +0 -154
  301. package/dist/__tests__/realtime-review-lifecycle.test.js.map +0 -1
  302. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts +0 -2
  303. package/dist/__tests__/realtime-session-cancel-usage.test.d.ts.map +0 -1
  304. package/dist/__tests__/realtime-session-cancel-usage.test.js +0 -230
  305. package/dist/__tests__/realtime-session-cancel-usage.test.js.map +0 -1
  306. package/dist/__tests__/realtime-session-channels.test.d.ts +0 -2
  307. package/dist/__tests__/realtime-session-channels.test.d.ts.map +0 -1
  308. package/dist/__tests__/realtime-session-channels.test.js +0 -252
  309. package/dist/__tests__/realtime-session-channels.test.js.map +0 -1
  310. package/dist/__tests__/realtime-session-client-tools.test.d.ts +0 -2
  311. package/dist/__tests__/realtime-session-client-tools.test.d.ts.map +0 -1
  312. package/dist/__tests__/realtime-session-client-tools.test.js +0 -103
  313. package/dist/__tests__/realtime-session-client-tools.test.js.map +0 -1
  314. package/dist/__tests__/realtime-session-minimized.test.d.ts +0 -2
  315. package/dist/__tests__/realtime-session-minimized.test.d.ts.map +0 -1
  316. package/dist/__tests__/realtime-session-minimized.test.js +0 -32
  317. package/dist/__tests__/realtime-session-minimized.test.js.map +0 -1
  318. package/dist/__tests__/realtime-session-mint.test.d.ts +0 -2
  319. package/dist/__tests__/realtime-session-mint.test.d.ts.map +0 -1
  320. package/dist/__tests__/realtime-session-mint.test.js +0 -69
  321. package/dist/__tests__/realtime-session-mint.test.js.map +0 -1
  322. package/dist/__tests__/realtime-session-policy.test.d.ts +0 -2
  323. package/dist/__tests__/realtime-session-policy.test.d.ts.map +0 -1
  324. package/dist/__tests__/realtime-session-policy.test.js +0 -303
  325. package/dist/__tests__/realtime-session-policy.test.js.map +0 -1
  326. package/dist/__tests__/realtime-session-review.service.test.d.ts +0 -2
  327. package/dist/__tests__/realtime-session-review.service.test.d.ts.map +0 -1
  328. package/dist/__tests__/realtime-session-review.service.test.js +0 -743
  329. package/dist/__tests__/realtime-session-review.service.test.js.map +0 -1
  330. package/dist/__tests__/realtime-session-state.test.d.ts +0 -2
  331. package/dist/__tests__/realtime-session-state.test.d.ts.map +0 -1
  332. package/dist/__tests__/realtime-session-state.test.js +0 -83
  333. package/dist/__tests__/realtime-session-state.test.js.map +0 -1
  334. package/dist/__tests__/realtime-session-timeline-card.test.d.ts +0 -2
  335. package/dist/__tests__/realtime-session-timeline-card.test.d.ts.map +0 -1
  336. package/dist/__tests__/realtime-session-timeline-card.test.js +0 -106
  337. package/dist/__tests__/realtime-session-timeline-card.test.js.map +0 -1
  338. package/dist/__tests__/realtime-session-timeline.test.d.ts +0 -2
  339. package/dist/__tests__/realtime-session-timeline.test.d.ts.map +0 -1
  340. package/dist/__tests__/realtime-session-timeline.test.js +0 -142
  341. package/dist/__tests__/realtime-session-timeline.test.js.map +0 -1
  342. package/dist/__tests__/realtime-sessions-adapter.test.d.ts +0 -19
  343. package/dist/__tests__/realtime-sessions-adapter.test.d.ts.map +0 -1
  344. package/dist/__tests__/realtime-sessions-adapter.test.js +0 -188
  345. package/dist/__tests__/realtime-sessions-adapter.test.js.map +0 -1
  346. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts +0 -2
  347. package/dist/__tests__/realtime-surface-panel-prefs.test.d.ts.map +0 -1
  348. package/dist/__tests__/realtime-surface-panel-prefs.test.js +0 -100
  349. package/dist/__tests__/realtime-surface-panel-prefs.test.js.map +0 -1
  350. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts +0 -2
  351. package/dist/__tests__/realtime-surface-tabs-model.test.d.ts.map +0 -1
  352. package/dist/__tests__/realtime-surface-tabs-model.test.js +0 -193
  353. package/dist/__tests__/realtime-surface-tabs-model.test.js.map +0 -1
  354. package/dist/__tests__/remote-browser-audio-player.test.d.ts +0 -2
  355. package/dist/__tests__/remote-browser-audio-player.test.d.ts.map +0 -1
  356. package/dist/__tests__/remote-browser-audio-player.test.js +0 -137
  357. package/dist/__tests__/remote-browser-audio-player.test.js.map +0 -1
  358. package/dist/__tests__/remote-browser-channel.test.d.ts +0 -2
  359. package/dist/__tests__/remote-browser-channel.test.d.ts.map +0 -1
  360. package/dist/__tests__/remote-browser-channel.test.js +0 -585
  361. package/dist/__tests__/remote-browser-channel.test.js.map +0 -1
  362. package/dist/__tests__/search-service.test.d.ts +0 -2
  363. package/dist/__tests__/search-service.test.d.ts.map +0 -1
  364. package/dist/__tests__/search-service.test.js +0 -114
  365. package/dist/__tests__/search-service.test.js.map +0 -1
  366. package/dist/__tests__/slot-defaults.test.d.ts +0 -24
  367. package/dist/__tests__/slot-defaults.test.d.ts.map +0 -1
  368. package/dist/__tests__/slot-defaults.test.js +0 -63
  369. package/dist/__tests__/slot-defaults.test.js.map +0 -1
  370. package/dist/__tests__/toast-service.test.d.ts +0 -2
  371. package/dist/__tests__/toast-service.test.d.ts.map +0 -1
  372. package/dist/__tests__/toast-service.test.js +0 -143
  373. package/dist/__tests__/toast-service.test.js.map +0 -1
  374. package/dist/__tests__/user-authorization.test.d.ts +0 -2
  375. package/dist/__tests__/user-authorization.test.d.ts.map +0 -1
  376. package/dist/__tests__/user-authorization.test.js +0 -97
  377. package/dist/__tests__/user-authorization.test.js.map +0 -1
  378. package/dist/__tests__/voice-session-narration.test.d.ts +0 -2
  379. package/dist/__tests__/voice-session-narration.test.d.ts.map +0 -1
  380. package/dist/__tests__/voice-session-narration.test.js +0 -609
  381. package/dist/__tests__/voice-session-narration.test.js.map +0 -1
  382. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts +0 -2
  383. package/dist/__tests__/whiteboard-artifact-viewer.test.d.ts.map +0 -1
  384. package/dist/__tests__/whiteboard-artifact-viewer.test.js +0 -101
  385. package/dist/__tests__/whiteboard-artifact-viewer.test.js.map +0 -1
  386. package/dist/__tests__/whiteboard-channel.test.d.ts +0 -2
  387. package/dist/__tests__/whiteboard-channel.test.d.ts.map +0 -1
  388. package/dist/__tests__/whiteboard-channel.test.js +0 -260
  389. package/dist/__tests__/whiteboard-channel.test.js.map +0 -1
  390. package/dist/__tests__/whiteboard-restore-state.test.d.ts +0 -2
  391. package/dist/__tests__/whiteboard-restore-state.test.d.ts.map +0 -1
  392. package/dist/__tests__/whiteboard-restore-state.test.js +0 -108
  393. package/dist/__tests__/whiteboard-restore-state.test.js.map +0 -1
  394. package/dist/lib/components/mention/mention-dropdown.component.d.ts +0 -53
  395. package/dist/lib/components/mention/mention-dropdown.component.d.ts.map +0 -1
  396. package/dist/lib/components/mention/mention-dropdown.component.js +0 -276
  397. package/dist/lib/components/mention/mention-dropdown.component.js.map +0 -1
  398. package/dist/lib/components/mention/mention-editor.component.d.ts +0 -256
  399. package/dist/lib/components/mention/mention-editor.component.d.ts.map +0 -1
  400. package/dist/lib/components/mention/mention-editor.component.js +0 -1233
  401. package/dist/lib/components/mention/mention-editor.component.js.map +0 -1
  402. package/dist/lib/components/message/message-input-box.component.d.ts +0 -137
  403. package/dist/lib/components/message/message-input-box.component.d.ts.map +0 -1
  404. package/dist/lib/components/message/message-input-box.component.js +0 -321
  405. package/dist/lib/components/message/message-input-box.component.js.map +0 -1
@@ -1,7 +1,9 @@
1
1
  import { Component, ElementRef, EventEmitter, HostListener, Input, Output, ChangeDetectorRef, NgZone, ViewChild, inject } from '@angular/core';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import { UserInfoEngine } from '@memberjunction/core-entities';
4
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
4
5
  import { SharedGenericModule } from '@memberjunction/ng-shared-generic';
6
+ import { MJStorageMediaPlayerComponent } from '@memberjunction/ng-media-player';
5
7
  import { RealtimeSessionService } from '../../services/realtime-session.service';
6
8
  import { BuildReviewThreadItems } from '../../services/realtime-session-review.service';
7
9
  import { RealtimeSessionState } from './realtime-session-state';
@@ -12,16 +14,19 @@ import { RealtimeComposerComponent } from './realtime-composer.component';
12
14
  import { RealtimeSurfaceTabsComponent } from './realtime-surface-tabs.component';
13
15
  import { ClampSurfacePanelWidth, DefaultSurfacePanelWidth, IsSurfacePanelDrag, ParseSurfacePanelPref, SerializeSurfacePanelPref, SurfacePanelDragWidth, SURFACE_PANEL_COLLAPSED_WIDTH, SURFACE_PANEL_DEFAULT_WIDTH, SURFACE_PANEL_PREF_KEY } from './realtime-surface-panel-prefs';
14
16
  import { RealtimeDisclosureModel, SerializeUxMilestones, REALTIME_UX_PREF_KEY } from './realtime-disclosure';
17
+ import { resolveRealtimeUi, DEFAULT_REALTIME_UI_INPUTS, DEFAULT_REALTIME_UI_SIGNALS } from './realtime-ui-config';
15
18
  import { RealtimeAudioVisualSmoother } from './realtime-audio-visuals';
16
19
  import { ShouldRemoveReviewWhiteboardTab } from './realtime-surface-tabs.model';
20
+ import { ShouldRegisterChannelTabUpFront } from './realtime-surface-tab-style';
17
21
  import { RealtimeWhiteboardBoardComponent, WhiteboardState } from '@memberjunction/ng-whiteboard';
18
22
  import * as i0 from "@angular/core";
19
23
  import * as i1 from "@memberjunction/ng-shared-generic";
20
24
  import * as i2 from "@angular/common";
21
25
  const _c0 = ["reviewBoardTpl"];
26
+ const _c1 = ["recordingTpl"];
22
27
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_4_Template(rf, ctx) { if (rf & 1) {
23
- i0.ɵɵelementStart(0, "div", 5);
24
- i0.ɵɵelement(1, "i", 11);
28
+ i0.ɵɵelementStart(0, "div", 6);
29
+ i0.ɵɵelement(1, "i", 12);
25
30
  i0.ɵɵtext(2);
26
31
  i0.ɵɵelementEnd();
27
32
  } if (rf & 2) {
@@ -30,27 +35,27 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_4_Template(rf
30
35
  i0.ɵɵtextInterpolate1(" ", ctx_r1.State.Narration, " ");
31
36
  } }
32
37
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
33
- i0.ɵɵelementStart(0, "div", 7);
34
- i0.ɵɵelement(1, "mj-loading", 12);
38
+ i0.ɵɵelementStart(0, "div", 8);
39
+ i0.ɵɵelement(1, "mj-loading", 13);
35
40
  i0.ɵɵelementEnd();
36
41
  } }
37
42
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_7_Template(rf, ctx) { if (rf & 1) {
38
43
  const _r3 = i0.ɵɵgetCurrentView();
39
- i0.ɵɵelementStart(0, "div", 8)(1, "div", 13);
40
- i0.ɵɵelement(2, "span", 14)(3, "span", 15)(4, "span", 16)(5, "div", 17);
44
+ i0.ɵɵelementStart(0, "div", 9)(1, "div", 14);
45
+ i0.ɵɵelement(2, "span", 15)(3, "span", 16)(4, "span", 17)(5, "div", 18);
41
46
  i0.ɵɵelementEnd();
42
- i0.ɵɵelementStart(6, "div", 18);
47
+ i0.ɵɵelementStart(6, "div", 19);
43
48
  i0.ɵɵelement(7, "span")(8, "span")(9, "span")(10, "span")(11, "span")(12, "span")(13, "span")(14, "span")(15, "span");
44
49
  i0.ɵɵelementEnd();
45
- i0.ɵɵelementStart(16, "div", 19);
50
+ i0.ɵɵelementStart(16, "div", 20);
46
51
  i0.ɵɵtext(17);
47
52
  i0.ɵɵelementEnd();
48
- i0.ɵɵelementStart(18, "div", 20);
53
+ i0.ɵɵelementStart(18, "div", 21);
49
54
  i0.ɵɵtext(19);
50
55
  i0.ɵɵelementEnd();
51
- i0.ɵɵelementStart(20, "button", 21);
56
+ i0.ɵɵelementStart(20, "button", 22);
52
57
  i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_7_Template_button_click_20_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnTextReveal()); });
53
- i0.ɵɵelement(21, "i", 22);
58
+ i0.ɵɵelement(21, "i", 23);
54
59
  i0.ɵɵtext(22, " Show the conversation ");
55
60
  i0.ɵɵelementEnd()();
56
61
  } if (rf & 2) {
@@ -66,7 +71,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_7_Template(rf
66
71
  } }
67
72
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template(rf, ctx) { if (rf & 1) {
68
73
  const _r5 = i0.ɵɵgetCurrentView();
69
- i0.ɵɵelementStart(0, "mj-realtime-session-thread", 23);
74
+ i0.ɵɵelementStart(0, "mj-realtime-session-thread", 24);
70
75
  i0.ɵɵlistener("OpenRunRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template_mj_realtime_session_thread_OpenRunRequested_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnOpenRunRequested($event)); })("OpenArtifactRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template_mj_realtime_session_thread_OpenArtifactRequested_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnOpenArtifactRequested($event)); })("CancelRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template_mj_realtime_session_thread_CancelRequested_0_listener($event) { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnCancelDelegation($event)); });
71
76
  i0.ɵɵelementEnd();
72
77
  } if (rf & 2) {
@@ -79,18 +84,18 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Conditional
79
84
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template(rf, ctx) { if (rf & 1) {
80
85
  const _r6 = i0.ɵɵgetCurrentView();
81
86
  i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Conditional_0_Template, 1, 0, "mj-realtime-channel-strip");
82
- i0.ɵɵelementStart(1, "mj-realtime-composer", 24);
83
- i0.ɵɵlistener("OpenChanged", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_OpenChanged_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnComposerOpenChanged($event)); })("CaptionsToggled", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_CaptionsToggled_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnCaptionsToggled($event)); })("DetailsToggled", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_DetailsToggled_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDetailsToggled()); })("EndRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_EndRequested_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnEndCall()); });
87
+ i0.ɵɵelementStart(1, "mj-realtime-composer", 25);
88
+ i0.ɵɵlistener("OpenChanged", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_OpenChanged_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnComposerOpenChanged($event)); })("CaptionsToggled", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_CaptionsToggled_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnCaptionsToggled($event)); })("DetailsToggled", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_DetailsToggled_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnDetailsToggled()); })("MuteChanged", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_MuteChanged_1_listener($event) { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnComposerMuteChanged($event)); })("EndRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template_mj_realtime_composer_EndRequested_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnEndCall()); });
84
89
  i0.ɵɵelementEnd();
85
90
  } if (rf & 2) {
86
91
  const ctx_r1 = i0.ɵɵnextContext(2);
87
- i0.ɵɵconditional(ctx_r1.Disclosure.ShowComposer ? 0 : -1);
92
+ i0.ɵɵconditional(ctx_r1.ChannelStripVisible ? 0 : -1);
88
93
  i0.ɵɵadvance();
89
- i0.ɵɵproperty("Open", ctx_r1.ComposerOpen)("CaptionsOn", ctx_r1.ShowCaptions)("ShowDetails", ctx_r1.ShowDetailsControl)("DetailsOn", ctx_r1.DetailsPeek);
94
+ i0.ɵɵproperty("Open", ctx_r1.ComposerOpen)("Compact", ctx_r1.Ui.compact && ctx_r1.Ui.chrome === "orb")("CaptionsOn", ctx_r1.ShowCaptions)("IsMuted", ctx_r1.Muted)("ShowDetails", ctx_r1.ShowDetailsControl)("DetailsOn", ctx_r1.DetailsPeek);
90
95
  } }
91
96
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditional_0_Template(rf, ctx) { if (rf & 1) {
92
97
  const _r8 = i0.ɵɵgetCurrentView();
93
- i0.ɵɵelementStart(0, "div", 28);
98
+ i0.ɵɵelementStart(0, "div", 29);
94
99
  i0.ɵɵlistener("mousedown", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditional_0_Template_div_mousedown_0_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnPanelResizeStart($event)); })("dblclick", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditional_0_Template_div_dblclick_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.OnPanelResizeReset()); });
95
100
  i0.ɵɵelementEnd();
96
101
  } if (rf & 2) {
@@ -99,8 +104,8 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditiona
99
104
  } }
100
105
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template(rf, ctx) { if (rf & 1) {
101
106
  const _r7 = i0.ɵɵgetCurrentView();
102
- i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditional_0_Template, 1, 2, "div", 25);
103
- i0.ɵɵelementStart(1, "div", 26)(2, "mj-realtime-surface-tabs", 27);
107
+ i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Conditional_0_Template, 1, 2, "div", 26);
108
+ i0.ɵɵelementStart(1, "div", 27)(2, "mj-realtime-surface-tabs", 28);
104
109
  i0.ɵɵlistener("OpenRunRequested", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template_mj_realtime_surface_tabs_OpenRunRequested_2_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnOpenRunRequested($event)); })("CollapsedChange", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template_mj_realtime_surface_tabs_CollapsedChange_2_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPanelCollapsedChange($event)); })("WideChanged", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template_mj_realtime_surface_tabs_WideChanged_2_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnPanelWideChanged($event)); });
105
110
  i0.ɵɵelementEnd()();
106
111
  } if (rf & 2) {
@@ -109,10 +114,10 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template(r
109
114
  i0.ɵɵadvance();
110
115
  i0.ɵɵstyleProp("width", ctx_r1.ChannelFocusMode ? null : ctx_r1.PanelAreaSize, "px");
111
116
  i0.ɵɵadvance();
112
- i0.ɵɵproperty("State", ctx_r1.State)("DevMode", ctx_r1.DevMode)("CurrentUser", ctx_r1.CurrentUser)("EnvironmentID", ctx_r1.EnvironmentID)("Fill", ctx_r1.ChannelFocusMode);
117
+ i0.ɵɵproperty("State", ctx_r1.State)("DevMode", ctx_r1.DevMode)("CurrentUser", ctx_r1.CurrentUser)("EnvironmentID", ctx_r1.EnvironmentID)("ShowActivityTab", ctx_r1.ShowActivityTab)("ExtraArtifacts", ctx_r1.ReviewArtifacts)("Fill", ctx_r1.ChannelFocusMode);
113
118
  } }
114
119
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Conditional_5_Template(rf, ctx) { if (rf & 1) {
115
- i0.ɵɵelementStart(0, "span", 32);
120
+ i0.ɵɵelementStart(0, "span", 33);
116
121
  i0.ɵɵtext(1);
117
122
  i0.ɵɵelementEnd();
118
123
  } if (rf & 2) {
@@ -121,27 +126,27 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Conditiona
121
126
  } }
122
127
  function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template(rf, ctx) { if (rf & 1) {
123
128
  const _r9 = i0.ɵɵgetCurrentView();
124
- i0.ɵɵelementStart(0, "div", 10);
125
- i0.ɵɵelement(1, "span", 29);
126
- i0.ɵɵelementStart(2, "span", 30)(3, "span", 31);
129
+ i0.ɵɵelementStart(0, "div", 11);
130
+ i0.ɵɵelement(1, "span", 30);
131
+ i0.ɵɵelementStart(2, "span", 31)(3, "span", 32);
127
132
  i0.ɵɵtext(4);
128
133
  i0.ɵɵelementEnd();
129
- i0.ɵɵconditionalCreate(5, RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Conditional_5_Template, 2, 1, "span", 32);
134
+ i0.ɵɵconditionalCreate(5, RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Conditional_5_Template, 2, 1, "span", 33);
130
135
  i0.ɵɵelementEnd();
131
- i0.ɵɵelementStart(6, "button", 33);
136
+ i0.ɵɵelementStart(6, "button", 34);
132
137
  i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillMute()); });
133
- i0.ɵɵelement(7, "i", 34);
138
+ i0.ɵɵelement(7, "i", 35);
134
139
  i0.ɵɵelementEnd();
135
- i0.ɵɵelementStart(8, "button", 35);
140
+ i0.ɵɵelementStart(8, "button", 36);
136
141
  i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template_button_click_8_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillExit()); });
137
- i0.ɵɵelement(9, "i", 36);
142
+ i0.ɵɵelement(9, "i", 37);
138
143
  i0.ɵɵelementEnd();
139
- i0.ɵɵelementStart(10, "button", 37);
144
+ i0.ɵɵelementStart(10, "button", 38);
140
145
  i0.ɵɵlistener("click", function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r9); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnFocusPillEnd()); });
141
- i0.ɵɵelement(11, "i", 38);
146
+ i0.ɵɵelement(11, "i", 39);
142
147
  i0.ɵɵelementEnd()();
143
148
  } if (rf & 2) {
144
- let tmp_6_0;
149
+ let tmp_7_0;
145
150
  const state_r4 = i0.ɵɵnextContext();
146
151
  const ctx_r1 = i0.ɵɵnextContext();
147
152
  i0.ɵɵadvance();
@@ -149,7 +154,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template(r
149
154
  i0.ɵɵadvance(3);
150
155
  i0.ɵɵtextInterpolate1("Voice for ", ctx_r1.AgentName);
151
156
  i0.ɵɵadvance();
152
- i0.ɵɵconditional((tmp_6_0 = ctx_r1.FocusStateLabel(state_r4)) ? 5 : -1, tmp_6_0);
157
+ i0.ɵɵconditional((tmp_7_0 = ctx_r1.FocusStateLabel(state_r4)) ? 5 : -1, tmp_7_0);
153
158
  i0.ɵɵadvance();
154
159
  i0.ɵɵclassProp("board-focus-pill__btn--active", ctx_r1.FocusPillMuted);
155
160
  i0.ɵɵproperty("title", ctx_r1.FocusPillMuted ? "Unmute" : "Mute");
@@ -159,18 +164,18 @@ function RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template(r
159
164
  } }
160
165
  function RealtimeSessionOverlayComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
161
166
  const _r1 = i0.ɵɵgetCurrentView();
162
- i0.ɵɵelementStart(0, "div", 2)(1, "div", 3)(2, "mj-realtime-agent-banner", 4);
167
+ i0.ɵɵelementStart(0, "div", 3)(1, "div", 4)(2, "mj-realtime-agent-banner", 5);
163
168
  i0.ɵɵpipe(3, "async");
164
- i0.ɵɵlistener("OpenSessionRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_OpenSessionRequested_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnOpenSessionRequested($event)); })("CaptionsToggled", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_CaptionsToggled_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnCaptionsToggled($event)); })("DevModeToggled", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_DevModeToggled_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDevModeToggled($event)); })("DensityChanged", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_DensityChanged_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDensityChanged($event)); })("MinimizeRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_MinimizeRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnMinimize()); })("PureAudioRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_PureAudioRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPureAudio()); })("EndRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_EndRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnEndCall()); })("StartLiveRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_StartLiveRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnStartLive()); })("CloseRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_CloseRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnReviewClose()); });
169
+ i0.ɵɵlistener("OpenSessionRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_OpenSessionRequested_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnOpenSessionRequested($event)); })("DevModeToggled", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_DevModeToggled_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDevModeToggled($event)); })("DensityChanged", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_DensityChanged_2_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDensityChanged($event)); })("MinimizeRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_MinimizeRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnMinimize()); })("StartLiveRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_StartLiveRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnStartLive()); })("CloseRequested", function RealtimeSessionOverlayComponent_Conditional_0_Template_mj_realtime_agent_banner_CloseRequested_2_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnReviewClose()); });
165
170
  i0.ɵɵelementEnd();
166
- i0.ɵɵconditionalCreate(4, RealtimeSessionOverlayComponent_Conditional_0_Conditional_4_Template, 3, 1, "div", 5);
167
- i0.ɵɵelementStart(5, "div", 6);
168
- i0.ɵɵconditionalCreate(6, RealtimeSessionOverlayComponent_Conditional_0_Conditional_6_Template, 2, 0, "div", 7)(7, RealtimeSessionOverlayComponent_Conditional_0_Conditional_7_Template, 23, 4, "div", 8)(8, RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template, 1, 5, "mj-realtime-session-thread", 9);
171
+ i0.ɵɵconditionalCreate(4, RealtimeSessionOverlayComponent_Conditional_0_Conditional_4_Template, 3, 1, "div", 6);
172
+ i0.ɵɵelementStart(5, "div", 7);
173
+ i0.ɵɵconditionalCreate(6, RealtimeSessionOverlayComponent_Conditional_0_Conditional_6_Template, 2, 0, "div", 8)(7, RealtimeSessionOverlayComponent_Conditional_0_Conditional_7_Template, 23, 4, "div", 9)(8, RealtimeSessionOverlayComponent_Conditional_0_Conditional_8_Template, 1, 5, "mj-realtime-session-thread", 10);
169
174
  i0.ɵɵelementEnd();
170
- i0.ɵɵconditionalCreate(9, RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template, 2, 5);
175
+ i0.ɵɵconditionalCreate(9, RealtimeSessionOverlayComponent_Conditional_0_Conditional_9_Template, 2, 7);
171
176
  i0.ɵɵelementEnd();
172
- i0.ɵɵconditionalCreate(10, RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template, 3, 8);
173
- i0.ɵɵconditionalCreate(11, RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template, 12, 11, "div", 10);
177
+ i0.ɵɵconditionalCreate(10, RealtimeSessionOverlayComponent_Conditional_0_Conditional_10_Template, 3, 10);
178
+ i0.ɵɵconditionalCreate(11, RealtimeSessionOverlayComponent_Conditional_0_Conditional_11_Template, 12, 11, "div", 11);
174
179
  i0.ɵɵelementEnd();
175
180
  } if (rf & 2) {
176
181
  const state_r4 = ctx;
@@ -178,7 +183,7 @@ function RealtimeSessionOverlayComponent_Conditional_0_Template(rf, ctx) { if (r
178
183
  i0.ɵɵclassProp("call-overlay--hidden", ctx_r1.Hidden)("call-overlay--review", ctx_r1.IsReviewing)("board-focus", ctx_r1.ChannelFocusMode);
179
184
  i0.ɵɵattribute("aria-label", ctx_r1.IsReviewing ? "Past session review" : "Live voice session");
180
185
  i0.ɵɵadvance(2);
181
- i0.ɵɵproperty("State", state_r4)("AgentName", ctx_r1.AgentName)("ModelName", ctx_r1.IsReviewing ? null : i0.ɵɵpipeBind1(3, 30, ctx_r1.ModelName$))("DevMode", ctx_r1.DevMode)("SessionID", ctx_r1.IsReviewing ? ctx_r1.ReviewSessionID : ctx_r1.SessionID)("ReviewMode", ctx_r1.IsReviewing)("ReviewStartedAt", ctx_r1.ReviewStartedAt)("ReviewClosedAt", ctx_r1.ReviewClosedAt)("ReviewCloseReason", ctx_r1.ReviewCloseReason)("CaptionsOn", ctx_r1.ShowCaptions)("ShowCaptionsControl", !ctx_r1.IsReviewing)("ShowGear", !ctx_r1.IsReviewing && ctx_r1.Disclosure.ShowGear)("ShowEnd", !ctx_r1.IsReviewing && ctx_r1.Disclosure.ShowComposer)("ShowMinimize", !ctx_r1.IsReviewing)("ShowPureAudio", !ctx_r1.IsReviewing && ctx_r1.Disclosure.SessionLevel > 0)("Density", ctx_r1.Disclosure.Milestones.Density);
186
+ i0.ɵɵproperty("State", state_r4)("Compact", !ctx_r1.IsReviewing && ctx_r1.Ui.compact && ctx_r1.Ui.chrome === "orb")("AgentName", ctx_r1.AgentName)("ModelName", ctx_r1.IsReviewing ? null : i0.ɵɵpipeBind1(3, 28, ctx_r1.ModelName$))("DevMode", ctx_r1.DevMode)("SessionID", ctx_r1.IsReviewing ? ctx_r1.ReviewSessionID : ctx_r1.SessionID)("ReviewMode", ctx_r1.IsReviewing)("ReviewStartedAt", ctx_r1.ReviewStartedAt)("ReviewClosedAt", ctx_r1.ReviewClosedAt)("ReviewCloseReason", ctx_r1.ReviewCloseReason)("ShowGear", !ctx_r1.IsReviewing && ctx_r1.Ui.showGear)("ShowDensityPicker", ctx_r1.Ui.showDensityPicker)("ShowMinimize", !ctx_r1.IsReviewing && ctx_r1.Ui.showMinimize)("Density", ctx_r1.Disclosure.Milestones.Density);
182
187
  i0.ɵɵadvance(2);
183
188
  i0.ɵɵconditional(!ctx_r1.IsReviewing && ctx_r1.State.Narration && !ctx_r1.State.ActiveCallId && ctx_r1.Disclosure.ShowThread ? 4 : -1);
184
189
  i0.ɵɵadvance();
@@ -193,8 +198,8 @@ function RealtimeSessionOverlayComponent_Conditional_0_Template(rf, ctx) { if (r
193
198
  i0.ɵɵconditional(!ctx_r1.IsReviewing && ctx_r1.ChannelFocusMode ? 11 : -1);
194
199
  } }
195
200
  function RealtimeSessionOverlayComponent_ng_template_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
196
- i0.ɵɵelementStart(0, "div", 39);
197
- i0.ɵɵelement(1, "mj-realtime-whiteboard", 40);
201
+ i0.ɵɵelementStart(0, "div", 40);
202
+ i0.ɵɵelement(1, "mj-realtime-whiteboard", 41);
198
203
  i0.ɵɵelementEnd();
199
204
  } if (rf & 2) {
200
205
  const ctx_r1 = i0.ɵɵnextContext(2);
@@ -202,11 +207,20 @@ function RealtimeSessionOverlayComponent_ng_template_2_Conditional_0_Template(rf
202
207
  i0.ɵɵproperty("State", ctx)("AgentName", ctx_r1.AgentName)("ReadOnly", true);
203
208
  } }
204
209
  function RealtimeSessionOverlayComponent_ng_template_2_Template(rf, ctx) { if (rf & 1) {
205
- i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_ng_template_2_Conditional_0_Template, 2, 3, "div", 39);
210
+ i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_ng_template_2_Conditional_0_Template, 2, 3, "div", 40);
206
211
  } if (rf & 2) {
207
- let tmp_2_0;
212
+ let tmp_3_0;
208
213
  const ctx_r1 = i0.ɵɵnextContext();
209
- i0.ɵɵconditional((tmp_2_0 = ctx_r1.ReviewWhiteboard) ? 0 : -1, tmp_2_0);
214
+ i0.ɵɵconditional((tmp_3_0 = ctx_r1.ReviewWhiteboard) ? 0 : -1, tmp_3_0);
215
+ } }
216
+ function RealtimeSessionOverlayComponent_ng_template_4_Template(rf, ctx) { if (rf & 1) {
217
+ i0.ɵɵelementStart(0, "div", 42);
218
+ i0.ɵɵelement(1, "mj-storage-media-player", 43);
219
+ i0.ɵɵelementEnd();
220
+ } if (rf & 2) {
221
+ const ctx_r1 = i0.ɵɵnextContext();
222
+ i0.ɵɵadvance();
223
+ i0.ɵɵproperty("FileID", ctx_r1.ReviewRecordingFileID)("Transcript", ctx_r1.ReviewCues)("Provider", ctx_r1.Provider);
210
224
  } }
211
225
  /**
212
226
  * The "call mode" overlay for a live real-time voice session. Hosted by the
@@ -255,7 +269,7 @@ function RealtimeSessionOverlayComponent_ng_template_2_Template(rf, ctx) { if (r
255
269
  * controls collapse to a single "Start live session" button ({@link StartLiveRequested},
256
270
  * which resumes by chaining `lastSessionId`) and a Close button ({@link ReviewClosed}).
257
271
  */
258
- export class RealtimeSessionOverlayComponent {
272
+ export class RealtimeSessionOverlayComponent extends BaseAngularComponent {
259
273
  _agentName = 'Sage';
260
274
  /**
261
275
  * True while the call is MINIMIZED: the overlay hides itself (CSS, not destruction) so
@@ -276,6 +290,101 @@ export class RealtimeSessionOverlayComponent {
276
290
  CurrentUser = null;
277
291
  /** The active environment id, threaded to the surface panel's artifact viewer. */
278
292
  EnvironmentID = '';
293
+ // ── Declarative UI configuration (host controls every aspect of the surface) ──
294
+ //
295
+ // Each input is a thin, PascalCase setter over a private slot in {@link _uiInputs}.
296
+ // When a host binds it, that field wins over the {@link UiConfig} bag (individual
297
+ // input precedence). Setting any of these recomputes the resolved view-model so the
298
+ // change takes effect on the next CD pass. Every field is optional and defaults to
299
+ // the historical behaviour via {@link DEFAULT_REALTIME_UI_INPUTS}, so existing call
300
+ // sites that bind none of them behave exactly as before.
301
+ //
302
+ // See {@link RealtimeUiInputs} / the package README for the full contract + recipes.
303
+ /**
304
+ * The chrome the widget renders: the ambient `'orb'`, the structured `'console'`, or
305
+ * `'auto'` (default) which starts as an orb and graduates to a console once the
306
+ * container is wide enough AND the user has revealed text. Mirrors
307
+ * {@link RealtimeUiInputs.chrome}.
308
+ *
309
+ * @example Force the rich console for a full-screen route:
310
+ * ```html
311
+ * <mj-realtime-session-overlay [Chrome]="'console'"></mj-realtime-session-overlay>
312
+ * ```
313
+ */
314
+ set Chrome(value) { this.setUiInput('chrome', value); }
315
+ get Chrome() { return this._uiInputs.chrome; }
316
+ /**
317
+ * In `'auto'` chrome, the container width (px) at/above which the widget may graduate
318
+ * to a console (still gated on text being revealed). Default `560`. Mirrors
319
+ * {@link RealtimeUiInputs.consoleBreakpointPx}.
320
+ */
321
+ set ConsoleBreakpointPx(value) { this.setUiInput('consoleBreakpointPx', value); }
322
+ get ConsoleBreakpointPx() { return this._uiInputs.consoleBreakpointPx; }
323
+ /**
324
+ * Force dense spacing/typography regardless of width (useful for tight mobile sheets).
325
+ * Default `false` — compactness is otherwise inferred from the container width.
326
+ */
327
+ set Compact(value) { this.setUiInput('compact', value); }
328
+ get Compact() { return this._uiInputs.compact; }
329
+ /** Fade non-essential controls when the session is idle (orb chrome only). Default `true`. */
330
+ set AutoHideControls(value) { this.setUiInput('autoHideControls', value); }
331
+ get AutoHideControls() { return this._uiInputs.autoHideControls; }
332
+ /**
333
+ * May the user reveal the transcript at all? `false` makes the widget a pure voice orb
334
+ * with no path to text (and never graduates to a console in `'auto'`). Default `true`.
335
+ */
336
+ set AllowTextReveal(value) { this.setUiInput('allowTextReveal', value); }
337
+ get AllowTextReveal() { return this._uiInputs.allowTextReveal; }
338
+ /** Show the captions toggle. Default `true`. */
339
+ set ShowCaptionsControl(value) { this.setUiInput('showCaptionsControl', value); }
340
+ get ShowCaptionsControl() { return this._uiInputs.showCaptionsControl; }
341
+ /** Show the density picker inside the gear menu. Default `true`. */
342
+ set ShowDensityPicker(value) { this.setUiInput('showDensityPicker', value); }
343
+ get ShowDensityPicker() { return this._uiInputs.showDensityPicker; }
344
+ /** Show the minimize control (collapse the call without ending it). Default `true`. */
345
+ set ShowMinimize(value) { this.setUiInput('showMinimize', value); }
346
+ get ShowMinimize() { return this._uiInputs.showMinimize; }
347
+ /** Show the end-call control. Default `true` (hosts that own their own end button may hide it). */
348
+ set ShowEnd(value) { this.setUiInput('showEnd', value); }
349
+ get ShowEnd() { return this._uiInputs.showEnd; }
350
+ /**
351
+ * Show the right-hand surface panel (whiteboard / browser / interactive channels).
352
+ * Default `true`; only renders in console chrome and once a panel is actually earned.
353
+ */
354
+ set ShowSurfacePanel(value) { this.setUiInput('showSurfacePanel', value); }
355
+ get ShowSurfacePanel() { return this._uiInputs.showSurfacePanel; }
356
+ /** Show the channel strip (active interactive channels). Default `true`. */
357
+ set ShowChannels(value) { this.setUiInput('showChannels', value); }
358
+ get ShowChannels() { return this._uiInputs.showChannels; }
359
+ /** Show the Activity rail/tab (delegations, artifacts timeline). Default `true`. */
360
+ set ShowActivityRail(value) { this.setUiInput('showActivityRail', value); }
361
+ get ShowActivityRail() { return this._uiInputs.showActivityRail; }
362
+ /**
363
+ * Show developer affordances ("open session / open run" links). Default `true`, but
364
+ * still additionally gated by the per-session dev-mode toggle — a hard ceiling, not a
365
+ * force-on.
366
+ */
367
+ set ShowDevLinks(value) { this.setUiInput('showDevLinks', value); }
368
+ get ShowDevLinks() { return this._uiInputs.showDevLinks; }
369
+ /** Allow drag-to-resize of the surface panel. Default `true`. */
370
+ set AllowResize(value) { this.setUiInput('allowResize', value); }
371
+ get AllowResize() { return this._uiInputs.allowResize; }
372
+ /**
373
+ * A programmatic override bag, merged UNDER the individual inputs — any field also set
374
+ * via a dedicated `@Input()` wins over its entry here. Lets a host configure the whole
375
+ * surface in one binding (e.g. a saved per-context preset) without enumerating a dozen
376
+ * attributes.
377
+ *
378
+ * @example
379
+ * ```html
380
+ * <mj-realtime-session-overlay [UiConfig]="leanOverlayPreset"></mj-realtime-session-overlay>
381
+ * ```
382
+ */
383
+ set UiConfig(value) {
384
+ this._uiConfigBag = value ?? null;
385
+ this.recomputeUi();
386
+ }
387
+ get UiConfig() { return this._uiConfigBag; }
279
388
  /**
280
389
  * SESSION REVIEW data: when set (and no live session is active) the overlay renders
281
390
  * the reviewed past session instead of a live call. Setting it populates the shared
@@ -287,7 +396,7 @@ export class RealtimeSessionOverlayComponent {
287
396
  return;
288
397
  }
289
398
  this._reviewData = value;
290
- if (value && !this.voice.IsActive) {
399
+ if (value && !this.realtime.IsActive) {
291
400
  this.enterReview(value);
292
401
  }
293
402
  else if (!value) {
@@ -313,8 +422,71 @@ export class RealtimeSessionOverlayComponent {
313
422
  StartLiveRequested = new EventEmitter();
314
423
  /** Review mode's Close: the host clears its review state, returning to the conversation. */
315
424
  ReviewClosed = new EventEmitter();
316
- voice = inject(RealtimeSessionService);
425
+ // ── Declarative UI outputs (observe everything the surface does) ─────────────
426
+ /** Emitted when the user minimizes the call view (the call stays live). */
427
+ Minimized = new EventEmitter();
428
+ /** Emitted when the user reveals the transcript (opens text from the orb). */
429
+ TextRevealed = new EventEmitter();
430
+ /** Emitted whenever the EFFECTIVE chrome flips between `'orb'` and `'console'`. */
431
+ ChromeChanged = new EventEmitter();
432
+ /** Emitted when the connection lifecycle state changes (e.g. connecting → listening). */
433
+ ConnectionStateChanged = new EventEmitter();
434
+ /** Emitted when the mic mute is toggled; payload is the new muted state. */
435
+ MuteChanged = new EventEmitter();
436
+ /** Emitted when the interface density changes. */
437
+ DensityChanged = new EventEmitter();
438
+ /** Emitted (px) when the user finishes resizing the surface panel. */
439
+ SurfacePanelResized = new EventEmitter();
440
+ /**
441
+ * A generic hook fired when ANY control is invoked — even one the host hid via inputs
442
+ * and re-rendered itself. Lets a host observe/react to the full control surface from a
443
+ * single binding.
444
+ */
445
+ ControlInvoked = new EventEmitter();
446
+ realtime = inject(RealtimeSessionService);
317
447
  cdr = inject(ChangeDetectorRef);
448
+ /**
449
+ * The consumer's individually-bound UI inputs (each dedicated `@Input()` writes its own
450
+ * slot here). Starts empty; only fields a host actually binds appear, so the resolver's
451
+ * defaults fill the rest. Merged OVER {@link _uiConfigBag} (individual inputs win).
452
+ */
453
+ _uiInputs = {};
454
+ /** The programmatic {@link UiConfig} override bag, merged UNDER the individual inputs. */
455
+ _uiConfigBag = null;
456
+ /**
457
+ * The latest measured width (px) of the rendered overlay element, fed to the resolver as
458
+ * {@link RealtimeUiSignals.containerWidthPx}. Maintained by a {@link ResizeObserver}; 0
459
+ * until the first measurement (the resolver treats 0 as "narrow", i.e. an orb in auto).
460
+ */
461
+ containerWidthPx = 0;
462
+ /** Observes the rendered overlay element so chrome re-resolves as the host resizes. */
463
+ resizeObserver = null;
464
+ /** The last connection state seen on {@link RealtimeSessionService.ConnectionState$}. */
465
+ currentConnectionState = 'closed';
466
+ /**
467
+ * The resolved, render-ready view-model — the SINGLE source of truth the template binds
468
+ * to (no `&&` chains in HTML). Rebuilt by {@link recomputeUi} whenever an input, the
469
+ * disclosure model, the connection state, the container width, or the channel/activity
470
+ * set changes. Read-only to consumers via the {@link Ui} getter.
471
+ */
472
+ // Seed from a dependency-free baseline — this runs as a field initializer, BEFORE the
473
+ // disclosure model / session state / ResizeObserver exist. recomputeUi() produces the real
474
+ // value once dependencies are ready (post-init + on every wired change source).
475
+ _ui = resolveRealtimeUi(DEFAULT_REALTIME_UI_INPUTS, DEFAULT_REALTIME_UI_SIGNALS);
476
+ /**
477
+ * The current resolved UI view-model. Every visibility/affordance decision the template
478
+ * makes reads from this — bind to its boolean fields rather than composing logic inline.
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * if (this.voice.Ui.chrome === 'console') { ... }
483
+ * ```
484
+ */
485
+ get Ui() {
486
+ return this._ui;
487
+ }
488
+ /** Local mic-muted state, the single source the resolver-independent controls reflect. */
489
+ Muted = false;
318
490
  _reviewData = null;
319
491
  /**
320
492
  * Set by {@link OnStartLive} just before emitting {@link StartLiveRequested}, so the
@@ -323,8 +495,16 @@ export class RealtimeSessionOverlayComponent {
323
495
  * a plain review close (clear everything).
324
496
  */
325
497
  pendingLiveContinuation = false;
326
- /** The reviewed chain's history artifacts, registered as unfocused surface tabs. */
498
+ /**
499
+ * The reviewed chain's history artifacts, surfaced inside the Activity tab's "Session
500
+ * artifacts" group (no longer their own tabs). Bound into the surface panel via
501
+ * {@link ReviewArtifacts}. Empty for a live session.
502
+ */
327
503
  reviewArtifacts = [];
504
+ /** The reviewed chain's history artifacts for the surface panel's Activity tab. */
505
+ get ReviewArtifacts() {
506
+ return this.reviewArtifacts;
507
+ }
328
508
  /**
329
509
  * True while the surface panel carries the REVIEW-registered (template-based, read-only)
330
510
  * Whiteboard tab. Drives the review→live continuation edge: when the resumed live
@@ -335,13 +515,27 @@ export class RealtimeSessionOverlayComponent {
335
515
  reviewWhiteboardTabRegistered = false;
336
516
  /** True while the overlay renders a PAST session (review data set, no live call). */
337
517
  get IsReviewing() {
338
- return this._reviewData !== null && !this.voice.IsActive;
518
+ return this._reviewData !== null && !this.realtime.IsActive;
339
519
  }
340
520
  /**
341
521
  * The reviewed session's rehydrated whiteboard, when it saved a parseable Whiteboard
342
522
  * channel state — rendered read-only by the review whiteboard tab. Null = no tab.
343
523
  */
344
524
  ReviewWhiteboard = null;
525
+ /**
526
+ * The `MJ: Files` id of the reviewed session's recording, or null when nothing was recorded.
527
+ * Bound into the storage media player, which resolves the authenticated audio itself.
528
+ */
529
+ get ReviewRecordingFileID() {
530
+ return this._reviewData?.RecordingFileID ?? null;
531
+ }
532
+ /**
533
+ * The reviewed session's transcript cues (built from its turns) for the recording player's
534
+ * transcript panel. Empty outside review.
535
+ */
536
+ ReviewCues = [];
537
+ /** True while the Recording tab is registered (a recording exists for the reviewed session). */
538
+ reviewRecordingTabRegistered = false;
345
539
  /** Shared session state — single source for the thread AND the activity rail. */
346
540
  State = new RealtimeSessionState();
347
541
  /**
@@ -365,9 +559,9 @@ export class RealtimeSessionOverlayComponent {
365
559
  */
366
560
  ComposerOpen = false;
367
561
  /** Live turn-state from the session service — drives the banner + connecting screen. */
368
- ConnectionState$ = this.voice.ConnectionState$;
562
+ ConnectionState$ = this.realtime.ConnectionState$;
369
563
  /** Server-reported realtime model name for the active session — shown subtly in the banner. */
370
- ModelName$ = this.voice.ModelName$;
564
+ ModelName$ = this.realtime.ModelName$;
371
565
  /**
372
566
  * Whether the conversation renders as TEXT (the thread) or stays voice-first (the
373
567
  * orb hero). A PERSISTED per-user preference (`mj.realtimeVoice.captions.v1`) —
@@ -383,7 +577,7 @@ export class RealtimeSessionOverlayComponent {
383
577
  DevMode = false;
384
578
  /** ID of the live server-side agent session record, for the banner's dev link. */
385
579
  get SessionID() {
386
- return this.voice.CurrentAgentSessionId;
580
+ return this.realtime.CurrentAgentSessionId;
387
581
  }
388
582
  /**
389
583
  * The tabbed surface panel (right panel) — channel registrations are forwarded to it.
@@ -395,11 +589,12 @@ export class RealtimeSessionOverlayComponent {
395
589
  set surfaceTabsRef(ref) {
396
590
  this.surfaceTabs = ref;
397
591
  if (ref) {
398
- // A (re)created panel starts with a FRESH tab model: artifact tabs self-recover
399
- // (the panel re-scans the session state) but CHANNEL tabs only registered when
400
- // ActiveChannels$ emitted at session start re-register the live set here so
401
- // hiding the panel (pure-audio return, Details off) never loses the Whiteboard.
402
- this.registerChannelTabs([...this.voice.ActiveChannels]);
592
+ // A (re)created panel starts with a FRESH tab model. Re-register the live channel set
593
+ // here (gated to whiteboard + already-used channels) so hiding the panel (pure-audio
594
+ // return, Details off) never loses the Whiteboard or an already-used channel's tab.
595
+ // Artifacts live in the Activity rail now (driven by session state), so there's
596
+ // nothing artifact-specific to recover on the panel.
597
+ this.registerChannelTabs([...this.realtime.ActiveChannels]);
403
598
  this.flushPendingChannelTabs();
404
599
  const reveal = this.pendingRevealKey;
405
600
  this.pendingRevealKey = null;
@@ -419,7 +614,7 @@ export class RealtimeSessionOverlayComponent {
419
614
  });
420
615
  }
421
616
  }
422
- /** The bottom dock — the T-to-type hotkey focuses its input. */
617
+ /** The bottom dock — type-to-compose seeds + focuses its input. */
423
618
  composer;
424
619
  /** Channel keys already auto-revealed this session (first activity only). */
425
620
  revealedChannelKeys = new Set();
@@ -429,6 +624,8 @@ export class RealtimeSessionOverlayComponent {
429
624
  prevActive = false;
430
625
  /** Template hosting the read-only review whiteboard (root-level, so always resolvable). */
431
626
  reviewBoardTpl;
627
+ /** Template hosting the review-mode recording player (root-level, so always resolvable). */
628
+ recordingTpl;
432
629
  /** Channel registrations received before the surface panel rendered (flushed in ngAfterViewInit). */
433
630
  pendingChannelTabs = [];
434
631
  /** True once the view (and the review-board template ref) exists. */
@@ -446,27 +643,174 @@ export class RealtimeSessionOverlayComponent {
446
643
  focusChannel = null;
447
644
  subs = [];
448
645
  constructor() {
646
+ super();
449
647
  this.loadPanelWidthPref();
450
648
  this.loadDisclosurePref();
451
649
  this.loadCaptionsPref();
452
- this.State.Attach(this.voice);
650
+ this.State.Attach(this.realtime);
453
651
  this.subs.push(
454
652
  // Re-render on merged-state changes; content arrival raises the disclosure level.
455
- this.State.Changed$.subscribe(() => this.onSessionStateChanged()), this.Disclosure.Changed$.subscribe(() => this.cdr.markForCheck()),
653
+ this.State.Changed$.subscribe(() => this.onSessionStateChanged()),
654
+ // Disclosure ratchet moved — re-resolve the view-model (thread/composer/gear gates).
655
+ this.Disclosure.Changed$.subscribe(() => this.recomputeUi()),
456
656
  // One surface tab per registry-resolved channel plugin (replays the current set).
457
- this.voice.ActiveChannels$.subscribe(channels => this.registerChannelTabs(channels)),
657
+ this.realtime.ActiveChannels$.subscribe(channels => { this.registerChannelTabs(channels); this.recomputeUi(); }),
458
658
  // Any channel may request the focus layout through its host context.
459
- this.voice.ChannelFocus$.subscribe(event => this.onChannelFocus(event.Channel, event.Focused)),
659
+ this.realtime.ChannelFocus$.subscribe(event => this.onChannelFocus(event.Channel, event.Focused)),
460
660
  // The agent ACTED on a channel — auto-reveal its surface tab on first activity.
461
- this.voice.ChannelActivity$.subscribe(plugin => this.onChannelActivity(plugin)),
661
+ this.realtime.ChannelActivity$.subscribe(plugin => this.onChannelActivity(plugin)),
462
662
  // Live/idle flips: reset/ratchet disclosure + re-evaluate the review-vs-live branch.
463
- this.voice.Active$.subscribe(active => this.onActiveChanged(active)));
663
+ this.realtime.Active$.subscribe(active => this.onActiveChanged(active)),
664
+ // Connection lifecycle drives chrome (the `connecting` loader) + the public output.
665
+ this.realtime.ConnectionState$.subscribe(state => this.onConnectionStateChanged(state)));
464
666
  }
465
667
  ngAfterViewInit() {
466
668
  this.viewReady = true;
669
+ this.startResizeObserver();
467
670
  this.flushPendingChannelTabs();
468
671
  this.registerReviewBoardTab();
469
672
  this.registerReviewArtifactTabs();
673
+ // Cues were already built by enterReview() when ReviewData was set; here we only need to
674
+ // (re)register the recording tab now that the #recordingTpl ref exists. The storage media
675
+ // player resolves the authenticated audio itself from the RecordingFileID.
676
+ this.registerReviewRecordingTab();
677
+ }
678
+ // ── Declarative UI config: inputs → signals → resolved view-model ────────────
679
+ /**
680
+ * Writes one individually-bound UI input into {@link _uiInputs} and re-resolves the
681
+ * view-model. `undefined` clears the slot (so the {@link UiConfig} bag / default applies).
682
+ * Called by every dedicated `@Input()` setter.
683
+ *
684
+ * @param key the {@link RealtimeUiInputs} field to set
685
+ * @param value the bound value, or `undefined` to clear the override
686
+ */
687
+ setUiInput(key, value) {
688
+ if (value === undefined) {
689
+ delete this._uiInputs[key];
690
+ }
691
+ else {
692
+ this._uiInputs[key] = value;
693
+ }
694
+ this.recomputeUi();
695
+ }
696
+ /**
697
+ * The effective consumer inputs: the {@link UiConfig} bag with the individually-bound
698
+ * inputs layered ON TOP, so a dedicated `@Input()` always wins over its bag entry.
699
+ */
700
+ get mergedUiInputs() {
701
+ return { ...(this._uiConfigBag ?? {}), ...this._uiInputs };
702
+ }
703
+ /**
704
+ * Maps the runtime {@link RealtimeConnectionState} onto the resolver's
705
+ * {@link RealtimeUiConnectionState}. `'closed'` (no live call) maps to `'idle'`; the
706
+ * other states pass through 1:1.
707
+ */
708
+ mapConnectionState(state) {
709
+ return state === 'closed' ? 'idle' : state;
710
+ }
711
+ /**
712
+ * Builds the live {@link RealtimeUiSignals} the resolver consumes, reading current facts
713
+ * from the disclosure model, the session/channel state, review mode, focus mode, and the
714
+ * {@link ResizeObserver}-measured width. Pure read — no side effects.
715
+ */
716
+ buildSignals() {
717
+ // Defensive: this can be reached before every dependency is wired (field-init / early
718
+ // change-detection). Fall back to the dependency-free baseline rather than throw — the
719
+ // next recompute, once everything exists, produces the real value.
720
+ const disclosure = this.Disclosure;
721
+ if (!disclosure) {
722
+ return DEFAULT_REALTIME_UI_SIGNALS;
723
+ }
724
+ const reviewing = this.IsReviewing;
725
+ return {
726
+ containerWidthPx: this.containerWidthPx ?? 0,
727
+ // Text intent = the user EXPLICITLY asked to see text this session — i.e. captions are on
728
+ // (the captions toggle, the hero's "Show the conversation", RevealText(), and SetCaptions()
729
+ // all route through ShowCaptions). NOT the disclosure ratchet: a power user still opens to
730
+ // the calm orb until they ask for text, matching the historical ShowHero = !ShowCaptions.
731
+ textRevealed: this.ShowCaptions,
732
+ disclosureShowThread: disclosure.ShowThread,
733
+ disclosureShowComposer: disclosure.ShowComposer,
734
+ disclosureShowPanel: disclosure.ShowPanel,
735
+ disclosureShowGear: disclosure.ShowGear,
736
+ // A surface to populate: the on-demand Details peek, or review (always has a surface).
737
+ surfacePanelEarned: this.DetailsPeek || reviewing,
738
+ hasChannels: (this.realtime?.ActiveChannels ?? []).some(c => c.HasSurface()),
739
+ hasActivity: (this.State?.Cards?.length ?? 0) > 0,
740
+ devMode: this.DevMode,
741
+ isReviewing: reviewing,
742
+ channelFocus: this.ChannelFocusMode,
743
+ connectionState: this.mapConnectionState(this.currentConnectionState)
744
+ };
745
+ }
746
+ /**
747
+ * Re-resolves {@link Ui} from the merged inputs + current signals and emits the chrome /
748
+ * connection-state outputs when they actually change. Safe to call on any of the wired
749
+ * change sources (input write, disclosure ratchet, connection flip, resize, channel set);
750
+ * the resolver is pure so redundant calls are cheap. Always marks for check.
751
+ */
752
+ recomputeUi() {
753
+ const next = resolveRealtimeUi(this.mergedUiInputs, this.buildSignals());
754
+ const prevChrome = this._ui.chrome;
755
+ this._ui = next;
756
+ if (next.chrome !== prevChrome) {
757
+ this.ChromeChanged.emit(next.chrome);
758
+ }
759
+ this.cdr.markForCheck();
760
+ }
761
+ /** Connection lifecycle changed: store it, re-resolve chrome, and surface the public output. */
762
+ onConnectionStateChanged(state) {
763
+ if (state === this.currentConnectionState) {
764
+ return;
765
+ }
766
+ this.currentConnectionState = state;
767
+ this.ConnectionStateChanged.emit(this.mapConnectionState(state));
768
+ this.recomputeUi();
769
+ }
770
+ // ── Container-width observation (drives the auto orb ↔ console graduation) ────
771
+ /**
772
+ * Starts observing the rendered overlay element's width so the resolver can graduate
773
+ * `'auto'` chrome to a console when the host gives it room. Guards SSR / environments
774
+ * without `ResizeObserver` (plain-node tests) — there the width simply stays 0 (an orb in
775
+ * auto), which is the safe ambient default. Idempotent.
776
+ */
777
+ startResizeObserver() {
778
+ if (this.resizeObserver || typeof ResizeObserver !== 'function') {
779
+ return;
780
+ }
781
+ const el = this.overlayElement() ?? this.hostRef.nativeElement;
782
+ if (!el) {
783
+ return;
784
+ }
785
+ // Seed an immediate measurement so the first resolve sees a real width. Deferred to a
786
+ // microtask (same reasoning as OnPanelWideChanged above): ngAfterViewInit runs inside the
787
+ // SAME change-detection pass that already checked bindings derived from Ui (e.g. the
788
+ // template's `@if (ShowPanelArea)` and `[Compact]`) using the field-initializer's width-0
789
+ // baseline. Recomputing synchronously here can flip chrome orb→console mid-pass and trip
790
+ // ExpressionChangedAfterItHasBeenCheckedError (NG0100). Landing the seed measurement in a
791
+ // fresh microtask lets it recompute in its own CD cycle instead.
792
+ Promise.resolve().then(() => {
793
+ this.applyContainerWidth(el.getBoundingClientRect ? el.getBoundingClientRect().width : 0);
794
+ });
795
+ this.resizeObserver = new ResizeObserver(entries => {
796
+ const width = entries[0]?.contentRect?.width ?? 0;
797
+ this.ngZone.run(() => Promise.resolve().then(() => this.applyContainerWidth(width)));
798
+ });
799
+ this.resizeObserver.observe(el);
800
+ }
801
+ /** Adopts a new measured width and re-resolves only when it actually changed. */
802
+ applyContainerWidth(width) {
803
+ const rounded = Math.round(width);
804
+ if (rounded === this.containerWidthPx) {
805
+ return;
806
+ }
807
+ this.containerWidthPx = rounded;
808
+ this.recomputeUi();
809
+ }
810
+ /** Disconnects the container-width observer. */
811
+ stopResizeObserver() {
812
+ this.resizeObserver?.disconnect();
813
+ this.resizeObserver = null;
470
814
  }
471
815
  // ── Surface-panel sizing (flex layout + pointer-drag handle; width persisted per-user) ──
472
816
  //
@@ -497,9 +841,16 @@ export class RealtimeSessionOverlayComponent {
497
841
  get PanelAreaSize() {
498
842
  return this.PanelCollapsed ? SURFACE_PANEL_COLLAPSED_WIDTH : this.PanelWidthPx;
499
843
  }
500
- /** Resizing is meaningless while collapsed or when the surface fills (focus mode). */
844
+ /**
845
+ * Resizing is meaningless while collapsed or when the surface fills (focus mode), and a
846
+ * host may hard-disable it via `[AllowResize]="false"` — in review the resolver's
847
+ * `allowResize` is false (no live surface to resize), so this also reads the host flag
848
+ * directly (defaulting to the historical `true`) rather than the resolved value, keeping
849
+ * drag available in review exactly as before.
850
+ */
501
851
  get PanelResizeDisabled() {
502
- return this.PanelCollapsed || this.ChannelFocusMode;
852
+ const hostAllows = this.mergedUiInputs.allowResize ?? DEFAULT_REALTIME_UI_INPUTS.allowResize;
853
+ return this.PanelCollapsed || this.ChannelFocusMode || !hostAllows;
503
854
  }
504
855
  OnPanelCollapsedChange(collapsed) {
505
856
  this.PanelCollapsed = collapsed;
@@ -509,7 +860,18 @@ export class RealtimeSessionOverlayComponent {
509
860
  OnPanelWideChanged(wide) {
510
861
  this.PanelWide = wide;
511
862
  if (this.userPanelWidth === null) {
512
- this.PanelWidthPx = DefaultSurfacePanelWidth(wide, this.hostWidth());
863
+ // This can fire synchronously from the surface-tabs child during the FIRST change-detection
864
+ // pass, while hostWidth() transitions 0 → real across that same pass — setting the bound width
865
+ // inline then trips ExpressionChangedAfterItHasBeenCheckedError (380 → 308). Defer to a
866
+ // microtask so the recompute lands in a fresh CD cycle. (Drag/reset handlers stay synchronous —
867
+ // those run on real user events, not during CD.)
868
+ Promise.resolve().then(() => {
869
+ if (this.userPanelWidth === null) {
870
+ this.PanelWidthPx = DefaultSurfacePanelWidth(this.PanelWide, this.hostWidth());
871
+ this.cdr.markForCheck();
872
+ }
873
+ });
874
+ return;
513
875
  }
514
876
  this.cdr.markForCheck();
515
877
  }
@@ -548,15 +910,21 @@ export class RealtimeSessionOverlayComponent {
548
910
  return;
549
911
  }
550
912
  this.teardownPanelResize();
913
+ let resizedTo = null;
551
914
  if (IsSurfacePanelDrag(this.panelResizeStartX, event.clientX)) {
552
915
  this.userPanelWidth = this.PanelWidthPx;
553
916
  this.persistPanelWidth(this.PanelWidthPx);
917
+ resizedTo = this.PanelWidthPx;
554
918
  }
555
919
  else {
556
920
  this.PanelWidthPx = this.panelResizeStartWidth;
557
921
  }
558
922
  this.ngZone.run(() => {
559
923
  this.IsPanelResizing = false;
924
+ // Surface the new width to the host only on a genuine drag (not a bare click).
925
+ if (resizedTo !== null) {
926
+ this.SurfacePanelResized.emit(resizedTo);
927
+ }
560
928
  this.cdr.markForCheck();
561
929
  });
562
930
  }
@@ -610,27 +978,50 @@ export class RealtimeSessionOverlayComponent {
610
978
  }
611
979
  // ── Progressive disclosure (the console that grows with you) ────────────────
612
980
  /**
613
- * True while the PURE-AUDIO hero (the orb) owns the main column. The captions
614
- * preference IS the text-vs-orb switch: captions off (the voice-first default) shows
615
- * the breathing orb at ANY disclosure level; captions on shows the thread. Review
616
- * always shows the thread.
981
+ * True while the PURE-AUDIO hero (the orb) owns the main column. Now reads from the
982
+ * resolved view-model ({@link Ui}): the resolver decides hero-vs-thread from the
983
+ * effective chrome + text intent + review, so host inputs (`[Chrome]`,
984
+ * `[AllowTextReveal]`, the breakpoint) gate it. Review still always shows the thread.
617
985
  */
618
986
  get ShowHero() {
619
- return !this.IsReviewing && !this.ShowCaptions;
987
+ return this._ui.showHero;
620
988
  }
621
989
  /**
622
- * Whether the surface-panel area renders. The tabs have their OWN control (the
623
- * Details toggle, plus the board auto-reveal and artifact-view requests) they are
624
- * deliberately DECOUPLED from typing/disclosure levels: revealing the composer must
625
- * never surprise the user with the whole tab panel.
990
+ * Whether the surface-panel area renders. Driven by the resolved {@link Ui.showSurfacePanel}
991
+ * (console + earned + host's `[ShowSurfacePanel]`) OR review mode, which always carries the
992
+ * panel. The Details toggle still earns the panel on demand (it flips `surfacePanelEarned`).
626
993
  */
627
994
  get ShowPanelArea() {
628
- return this.IsReviewing || this.DetailsPeek;
995
+ return this._ui.showSurfacePanel || this.IsReviewing;
629
996
  }
630
- /** The Details (tabs) toggle is always offered while live — it's the panel's one door. */
997
+ /** The Details (tabs) toggle is offered while live — it's the panel's one door. */
631
998
  get ShowDetailsControl() {
632
999
  return !this.IsReviewing;
633
1000
  }
1001
+ /**
1002
+ * Whether the active-channels strip renders. Preserves the historical disclosure gate
1003
+ * (level 2+, {@link RealtimeDisclosureModel.ShowComposer}) AND honours the host's
1004
+ * `[ShowChannels]` input + the resolver's focus rule: a host that sets `[ShowChannels]`
1005
+ * false hard-disables the strip, and channel-focus mode hides it. The strip component
1006
+ * itself self-hides when no channels are active, so this stays decluttered.
1007
+ */
1008
+ get ChannelStripVisible() {
1009
+ // Reads the RESOLVED Ui.showChannelStrip flag — which already ANDs the host's
1010
+ // [ShowChannels] input, an active channel, console chrome, and the no-focus rule.
1011
+ // In orb/compact chrome it is false, so the overlay never shows the "CHANNELS · Voice"
1012
+ // strip (the lean Calm-Orb surface). The strip component itself still self-hides when
1013
+ // no channels are active, keeping the console decluttered too.
1014
+ return this._ui.showChannelStrip;
1015
+ }
1016
+ /**
1017
+ * Whether the surface panel's Activity tab should be shown. Reads the resolved
1018
+ * {@link Ui.showActivityTab} (host's `[ShowActivityRail]` AND activity/review AND console)
1019
+ * OR review mode — a past session's activity is always relevant even before the resolver's
1020
+ * console gate would otherwise allow it.
1021
+ */
1022
+ get ShowActivityTab() {
1023
+ return this._ui.showActivityTab || this.IsReviewing;
1024
+ }
634
1025
  /** Reads the persisted disclosure milestones (tolerant; defaults to day one). */
635
1026
  loadDisclosurePref() {
636
1027
  try {
@@ -676,7 +1067,8 @@ export class RealtimeSessionOverlayComponent {
676
1067
  * A pure-audio user stays in pure audio until THEY ask for more.
677
1068
  */
678
1069
  onSessionStateChanged() {
679
- this.cdr.markForCheck();
1070
+ // State.Cards drives the resolver's hasActivity signal — re-resolve (also marks for check).
1071
+ this.recomputeUi();
680
1072
  }
681
1073
  /**
682
1074
  * The agent ACTED on a channel (a tool call routed to its local executor — e.g. the
@@ -692,16 +1084,25 @@ export class RealtimeSessionOverlayComponent {
692
1084
  }
693
1085
  this.revealedChannelKeys.add(plugin.ChannelName);
694
1086
  this.DetailsPeek = true; // the panel shows via the same on-demand mechanism Details uses
1087
+ // FIRST USE: a non-whiteboard channel was tab-less until now — register its tab
1088
+ // SYNCHRONOUSLY (before the reveal/focus below) so the channel exists to be revealed.
1089
+ // The whiteboard already has its tab from session start; re-registering is idempotent.
1090
+ if (plugin.HasSurface()) {
1091
+ this.registerPluginChannelTab(plugin);
1092
+ }
695
1093
  if (this.surfaceTabs) {
696
1094
  // Stream handler (a tool call), not a change-detection pass — reveal synchronously
697
- // so the board is the visible tab the instant the agent's first stroke lands.
1095
+ // so the board is the visible tab the instant the agent's first stroke lands. The
1096
+ // microtask-deferred RegisterChannelTab above is ordered before this RevealChannel,
1097
+ // so the tab is in place when the focus lands.
698
1098
  this.surfaceTabs.RevealChannel(plugin.ChannelName);
699
1099
  }
700
1100
  else {
701
1101
  // Panel not rendered yet (the peek just created it) — reveal once it exists.
702
1102
  this.pendingRevealKey = plugin.ChannelName;
703
1103
  }
704
- this.cdr.markForCheck();
1104
+ // DetailsPeek flipped — surfacePanelEarned changed; re-resolve (marks for check).
1105
+ this.recomputeUi();
705
1106
  }
706
1107
  /** Active$ edges: session start resets the volatile level; session end ratchets + persists. */
707
1108
  onActiveChanged(active) {
@@ -734,7 +1135,7 @@ export class RealtimeSessionOverlayComponent {
734
1135
  audioRafHandle = null;
735
1136
  /** Last attribute values written (attributes are only touched on change). */
736
1137
  lastAudioLive = null;
737
- lastVoiceDirection = null;
1138
+ lastDirection = null;
738
1139
  /** Starts the sampling loop (idempotent). Runs outside Angular — no CD per frame. */
739
1140
  startAudioVisualLoop() {
740
1141
  if (this.audioRafHandle !== null || typeof requestAnimationFrame !== 'function') {
@@ -747,7 +1148,7 @@ export class RealtimeSessionOverlayComponent {
747
1148
  if (this.Hidden) {
748
1149
  return; // minimized — skip the work, keep the loop armed
749
1150
  }
750
- const frame = this.audioSmoother.Next(this.voice.GetAudioActivity(), performance.now());
1151
+ const frame = this.audioSmoother.Next(this.realtime.GetAudioActivity(), performance.now());
751
1152
  this.applyAudioVisualFrame(frame);
752
1153
  };
753
1154
  this.audioRafHandle = requestAnimationFrame(tick);
@@ -761,7 +1162,7 @@ export class RealtimeSessionOverlayComponent {
761
1162
  }
762
1163
  this.audioSmoother.Reset();
763
1164
  this.lastAudioLive = null;
764
- this.lastVoiceDirection = null;
1165
+ this.lastDirection = null;
765
1166
  this.overlayElement()?.setAttribute('data-audio-live', 'false');
766
1167
  }
767
1168
  /** The rendered `.call-overlay` element (the :host is display:contents). */
@@ -788,8 +1189,8 @@ export class RealtimeSessionOverlayComponent {
788
1189
  for (let i = 0; i < frame.Bins.length; i++) {
789
1190
  el.style.setProperty(`--eq-${i + 1}`, frame.Bins[i].toFixed(3));
790
1191
  }
791
- if (frame.Direction !== this.lastVoiceDirection) {
792
- this.lastVoiceDirection = frame.Direction;
1192
+ if (frame.Direction !== this.lastDirection) {
1193
+ this.lastDirection = frame.Direction;
793
1194
  el.setAttribute('data-voice-dir', frame.Direction);
794
1195
  }
795
1196
  }
@@ -800,37 +1201,45 @@ export class RealtimeSessionOverlayComponent {
800
1201
  // Land on the marquee surface (channels lead the strip; Activity is pinned last).
801
1202
  setTimeout(() => this.surfaceTabs?.FocusFirstTab());
802
1203
  }
803
- this.cdr.markForCheck();
1204
+ this.ControlInvoked.emit('surface');
1205
+ // DetailsPeek drives surfacePanelEarned — re-resolve (marks for check).
1206
+ this.recomputeUi();
804
1207
  }
805
- /** The gear popover picked an interface density — apply + persist immediately. */
1208
+ /**
1209
+ * The gear popover picked an interface density — apply + persist + emit. `Simple` IS the
1210
+ * "pure audio" surface (the standalone headphones control was folded into the gear here): it
1211
+ * returns to the orb-only view AND makes it stick across refresh/next call, so picking it also
1212
+ * closes any open Details peek so the surface is genuinely orb-only — matching the old app-bar
1213
+ * "Pure audio" button exactly. In-session reveals ("Show the conversation", T-to-type, Details)
1214
+ * stay available and ephemeral; switching density back to Standard/Pro/Auto restores the console.
1215
+ */
806
1216
  OnDensityChanged(density) {
807
1217
  this.Disclosure.SetDensity(density);
808
1218
  this.persistDisclosure(SerializeUxMilestones(this.Disclosure.Milestones));
1219
+ if (density === 'simple') {
1220
+ this.DetailsPeek = false; // Simple = pure audio: collapse the surface peek back to the bare orb.
1221
+ }
1222
+ this.DensityChanged.emit(density);
1223
+ this.ControlInvoked.emit('density');
1224
+ // Density (and possibly DetailsPeek) changed — re-resolve (marks for check).
1225
+ this.recomputeUi();
809
1226
  }
810
1227
  /** The hero's "Show the conversation" affordance — turns the text preference on. */
811
1228
  OnTextReveal() {
812
1229
  this.OnCaptionsToggled(true);
813
- }
814
- /**
815
- * The app-bar's "Pure audio": returns to the orb-only surface AND makes it stick —
816
- * it sets the persisted interface density to `simple` (the same setting the gear
817
- * writes), so a refresh / next call still opens pure audio. In-session reveals
818
- * ("Show the conversation", T-to-type, Details) remain available and stay ephemeral;
819
- * the gear's density control switches back to Standard/Pro/Auto whenever wanted.
820
- */
821
- OnPureAudio() {
822
- this.Disclosure.SetDensity('simple');
823
- this.persistDisclosure(SerializeUxMilestones(this.Disclosure.Milestones));
824
- this.DetailsPeek = false;
825
- this.cdr.markForCheck();
1230
+ this.TextRevealed.emit();
1231
+ this.ControlInvoked.emit('reveal-text');
826
1232
  }
827
1233
  /** App-bar Minimize: hide the call view (CSS) — the call stays fully live. */
828
1234
  OnMinimize() {
829
- this.voice.SetMinimized(true);
1235
+ this.realtime.SetMinimized(true);
1236
+ this.Minimized.emit();
1237
+ this.ControlInvoked.emit('minimize');
830
1238
  }
831
1239
  /** App-bar / strip End: tear the session down, then notify the host. */
832
1240
  async OnEndCall() {
833
- await this.voice.EndVoiceSession();
1241
+ this.ControlInvoked.emit('end');
1242
+ await this.realtime.EndRealtimeSession();
834
1243
  this.Ended.emit();
835
1244
  }
836
1245
  /** Maps the realtime state onto the hero orb's `data-state` (active turn-states only). */
@@ -853,29 +1262,38 @@ export class RealtimeSessionOverlayComponent {
853
1262
  }
854
1263
  }
855
1264
  /**
856
- * T-TO-TYPE: pressing T during a live call reveals the composer (raising disclosure to
857
- * the engaged level when needed) and focuses its input typing always exists, it just
858
- * whispers until used. Ignored while review/minimized, with modifiers, or when an
859
- * editable element already has focus.
1265
+ * TYPE-TO-COMPOSE: during a live call, pressing any single printable character (with nothing
1266
+ * focused) reveals the composer, raises disclosure to the engaged level, AND seeds that very
1267
+ * character into the draft the user just starts typing and their first key is captured (the
1268
+ * old dedicated "T" key is gone; "t"/"T" now types like any other letter, and the visible
1269
+ * "press T to type" hint was removed in favour of this natural behaviour). Ignored while
1270
+ * review/minimized/inactive, with Ctrl/Cmd/Alt modifiers (don't hijack shortcuts), for
1271
+ * non-printable named keys (Enter/Tab/Arrow/Escape/F-keys → `key.length > 1`), for a bare
1272
+ * Space (ambiguous — no message starts with a space), or when an editable element / a
1273
+ * keyboard-capturing surface (the remote-browser canvas) already holds focus.
860
1274
  */
861
1275
  OnDocumentKeydown(event) {
862
- if (this.Hidden || this.IsReviewing || !this.voice.IsActive) {
1276
+ if (this.Hidden || this.IsReviewing || !this.realtime.IsActive) {
1277
+ return;
1278
+ }
1279
+ if (event.metaKey || event.ctrlKey || event.altKey) {
863
1280
  return;
864
1281
  }
865
- if ((event.key !== 't' && event.key !== 'T') || event.metaKey || event.ctrlKey || event.altKey) {
1282
+ if (event.key.length !== 1 || event.key === ' ') {
866
1283
  return;
867
1284
  }
868
1285
  if (this.isEditableTarget(event.target) || this.isKeyboardCapturingSurfaceFocused()) {
869
1286
  return;
870
1287
  }
871
1288
  event.preventDefault();
1289
+ const typedChar = event.key;
872
1290
  this.OnComposerOpenChanged(true);
873
- // The dock may have just been created — focus after this CD pass.
874
- setTimeout(() => this.composer?.FocusInput());
1291
+ // The dock may have just been created — seed the typed character + focus after this CD pass.
1292
+ setTimeout(() => this.composer?.AppendAndFocus(typedChar));
875
1293
  }
876
1294
  /**
877
1295
  * True when the event target is a native text-editing element (an input, textarea, or contentEditable),
878
- * where a bare letter should type — not trigger the T-to-type shortcut.
1296
+ * where a bare letter should type into that element — not trigger type-to-compose.
879
1297
  *
880
1298
  * @param eventTarget The keydown event's target.
881
1299
  * @returns Whether the target is an editable element.
@@ -898,20 +1316,22 @@ export class RealtimeSessionOverlayComponent {
898
1316
  return !!active && active.hasAttribute('data-mj-capture-keys');
899
1317
  }
900
1318
  /**
901
- * The composer dock opened (strip Type control / T hotkey) or closed (the dock's hide
902
- * control). Opening raises the 'engaged' milestone for the cross-session ratchet —
1319
+ * The composer dock opened (strip Type control / type-to-compose keystroke) or closed (the
1320
+ * dock's hide control). Opening raises the 'engaged' milestone for the cross-session ratchet —
903
1321
  * but the dock itself stays a per-session, user-owned toggle either way.
904
1322
  */
905
1323
  OnComposerOpenChanged(open) {
906
1324
  this.ComposerOpen = open;
907
1325
  if (open) {
908
- this.Disclosure.Raise('engaged');
1326
+ this.Disclosure.Raise('engaged'); // emits Changed$ → recomputeUi()
1327
+ this.ControlInvoked.emit('type');
909
1328
  setTimeout(() => this.composer?.FocusInput());
910
1329
  }
911
1330
  this.cdr.markForCheck();
912
1331
  }
913
1332
  ngOnDestroy() {
914
1333
  this.stopAudioVisualLoop();
1334
+ this.stopResizeObserver();
915
1335
  if (this.IsPanelResizing) {
916
1336
  this.teardownPanelResize();
917
1337
  this.IsPanelResizing = false;
@@ -952,10 +1372,14 @@ export class RealtimeSessionOverlayComponent {
952
1372
  setTimeout(() => this.surfaceTabs?.FocusArtifact(artifact));
953
1373
  }
954
1374
  /**
955
- * Registers one surface tab per active channel plugin THAT HAS A SURFACE (key/title/icon from the
956
- * plugin). Server-only channels ({@link BaseRealtimeChannelClient.HasSurface} === `false`) render no
957
- * tab their tools + perception are already wired by the session service; there is simply nothing
958
- * to show in the surface panel.
1375
+ * Registers a surface tab per active channel plugin THAT HAS A SURFACE but ONLY for
1376
+ * channels already in play: the WHITEBOARD tabs immediately (a user may be the first to
1377
+ * draw), every OTHER channel stays tab-less until the agent first USES it (its first
1378
+ * {@link onChannelActivity}). This keeps the strip decluttered to surfaces actually in
1379
+ * play instead of pre-listing every registry-resolved channel.
1380
+ *
1381
+ * Server-only channels ({@link BaseRealtimeChannelClient.HasSurface} === `false`) render no
1382
+ * tab — their tools + perception are wired by the session service regardless.
959
1383
  */
960
1384
  registerChannelTabs(channels) {
961
1385
  this.cleanupStaleReviewBoardTab(channels);
@@ -963,15 +1387,23 @@ export class RealtimeSessionOverlayComponent {
963
1387
  if (!plugin.HasSurface()) {
964
1388
  continue; // server-only channel — no surface to tab.
965
1389
  }
966
- this.RegisterChannelTab({
967
- Key: plugin.ChannelName,
968
- Title: plugin.TabTitle,
969
- Icon: plugin.TabIcon,
970
- Plugin: plugin
971
- });
1390
+ if (!ShouldRegisterChannelTabUpFront(plugin.ChannelName, this.realtime.HasChannelBeenUsed(plugin.ChannelName))) {
1391
+ continue; // not the whiteboard and not used yet — it earns its tab on first activity.
1392
+ }
1393
+ this.registerPluginChannelTab(plugin);
972
1394
  }
973
1395
  this.cdr.markForCheck();
974
1396
  }
1397
+ /** Registers (or upgrades) one channel plugin's surface tab on the panel. */
1398
+ registerPluginChannelTab(plugin) {
1399
+ this.RegisterChannelTab({
1400
+ Key: plugin.ChannelName,
1401
+ Title: plugin.TabTitle,
1402
+ Icon: plugin.TabIcon,
1403
+ Color: plugin.TabColor,
1404
+ Plugin: plugin
1405
+ });
1406
+ }
975
1407
  /**
976
1408
  * REVIEW→LIVE continuation edge: when the resumed live session resolves its channel set
977
1409
  * WITHOUT a Whiteboard channel, the review-registered read-only board tab is now a dead
@@ -984,7 +1416,7 @@ export class RealtimeSessionOverlayComponent {
984
1416
  if (!this.reviewWhiteboardTabRegistered) {
985
1417
  return;
986
1418
  }
987
- if (ShouldRemoveReviewWhiteboardTab(this.voice.IsActive, this.reviewWhiteboardTabRegistered, channels)) {
1419
+ if (ShouldRemoveReviewWhiteboardTab(this.realtime.IsActive, this.reviewWhiteboardTabRegistered, channels)) {
988
1420
  this.surfaceTabs?.RemoveTab('Whiteboard');
989
1421
  // Also drop a still-queued review registration (panel not rendered yet — rare but possible).
990
1422
  this.pendingChannelTabs = this.pendingChannelTabs.filter(r => !(r.Key === 'Whiteboard' && r.Content));
@@ -1011,14 +1443,24 @@ export class RealtimeSessionOverlayComponent {
1011
1443
  OnCaptionsToggled(on) {
1012
1444
  this.ShowCaptions = on;
1013
1445
  if (on) {
1014
- this.Disclosure.Raise('text');
1446
+ this.Disclosure.Raise('text'); // emits Changed$ → recomputeUi()
1015
1447
  }
1016
1448
  this.persistCaptionsPref();
1017
- this.cdr.markForCheck();
1449
+ this.ControlInvoked.emit('captions');
1450
+ // ShowCaptions feeds the resolver's `textRevealed` signal (buildSignals), so BOTH directions must
1451
+ // re-resolve the view-model. Turning ON already recomputes via Disclosure.Raise → Changed$, but
1452
+ // turning OFF raises nothing — a bare markForCheck() would re-render against a STALE _ui (still
1453
+ // showThread=true / showHero=false), so the hero orb wouldn't return until the next unrelated
1454
+ // interaction recomputed. recomputeUi() rebuilds _ui from the current ShowCaptions and marks for
1455
+ // check itself; it's pure/idempotent, so the redundant call in the ON case is harmless.
1456
+ this.recomputeUi();
1018
1457
  }
1019
1458
  /** Reflect the gear toggle from the controls into the dev affordances. */
1020
1459
  OnDevModeToggled(on) {
1021
1460
  this.DevMode = on;
1461
+ this.ControlInvoked.emit('gear');
1462
+ // devMode drives the resolver's showDevLinks signal — re-resolve (marks for check).
1463
+ this.recomputeUi();
1022
1464
  }
1023
1465
  /** A dev link asked to open a delegated agent run record. */
1024
1466
  OnOpenRunRequested(runId) {
@@ -1075,6 +1517,7 @@ export class RealtimeSessionOverlayComponent {
1075
1517
  this.State.LoadHistoricalItems(BuildReviewThreadItems(review));
1076
1518
  this.ReviewWhiteboard = this.parseReviewWhiteboard(review);
1077
1519
  this.reviewArtifacts = review.Artifacts ?? [];
1520
+ this.ReviewCues = this.buildReviewCues(review);
1078
1521
  if (this.viewReady) {
1079
1522
  // Let this CD pass create/refresh the surface panel before registering the tabs.
1080
1523
  // ngZone.run is REQUIRED: review often opens through the deep-link/query-param
@@ -1084,9 +1527,45 @@ export class RealtimeSessionOverlayComponent {
1084
1527
  setTimeout(() => this.ngZone.run(() => {
1085
1528
  this.registerReviewBoardTab();
1086
1529
  this.registerReviewArtifactTabs();
1530
+ this.registerReviewRecordingTab();
1087
1531
  }), 0);
1088
1532
  }
1089
- this.cdr.markForCheck();
1533
+ // isReviewing flipped on — review always wants the console (marks for check).
1534
+ this.recomputeUi();
1535
+ }
1536
+ /**
1537
+ * Builds time-aligned {@link MediaTranscriptCue}s from the reviewed session's turns, for the
1538
+ * storage media player's transcript panel. Skips turns with no text; derives each cue's start from
1539
+ * `UtteranceStartMs`, falling back to (turn-created − recording-start) ms, else 0. Sorts by start.
1540
+ */
1541
+ buildReviewCues(review) {
1542
+ const startedAt = review.RecordingStartedAt;
1543
+ const cues = [];
1544
+ (review.Turns ?? []).forEach((turn, index) => {
1545
+ const text = (turn.Text ?? turn.Message ?? '').trim();
1546
+ if (text.length === 0) {
1547
+ return;
1548
+ }
1549
+ cues.push({
1550
+ Id: turn.ID || `turn-${index}`,
1551
+ StartMs: this.reviewCueStartMs(turn, startedAt),
1552
+ EndMs: turn.UtteranceEndMs ?? undefined,
1553
+ SpeakerLabel: turn.Role === 'User' ? 'You' : review.AgentName,
1554
+ Text: text
1555
+ });
1556
+ });
1557
+ cues.sort((a, b) => a.StartMs - b.StartMs);
1558
+ return cues;
1559
+ }
1560
+ /** A review turn's media-relative start (ms): precise offset, else derived from t0, else 0. */
1561
+ reviewCueStartMs(turn, startedAt) {
1562
+ if (turn.UtteranceStartMs != null) {
1563
+ return turn.UtteranceStartMs;
1564
+ }
1565
+ if (startedAt && turn.__mj_CreatedAt) {
1566
+ return Math.max(0, turn.__mj_CreatedAt.getTime() - startedAt.getTime());
1567
+ }
1568
+ return 0;
1090
1569
  }
1091
1570
  /**
1092
1571
  * Leaves review. Two distinct exits:
@@ -1098,6 +1577,12 @@ export class RealtimeSessionOverlayComponent {
1098
1577
  */
1099
1578
  exitReview() {
1100
1579
  this.ReviewWhiteboard = null;
1580
+ this.ReviewCues = [];
1581
+ if (this.reviewRecordingTabRegistered) {
1582
+ this.surfaceTabs?.RemoveTab('Recording');
1583
+ this.pendingChannelTabs = this.pendingChannelTabs.filter(r => r.Key !== 'Recording');
1584
+ this.reviewRecordingTabRegistered = false;
1585
+ }
1101
1586
  if (this.pendingLiveContinuation) {
1102
1587
  this.pendingLiveContinuation = false;
1103
1588
  this.State.StartLiveContinuation();
@@ -1106,16 +1591,22 @@ export class RealtimeSessionOverlayComponent {
1106
1591
  this.reviewArtifacts = [];
1107
1592
  this.State.Clear();
1108
1593
  }
1109
- this.cdr.markForCheck();
1594
+ // isReviewing flipped off — re-resolve the chrome/gates (marks for check).
1595
+ this.recomputeUi();
1110
1596
  }
1111
- /** Registers the reviewed chain's history artifacts as UNFOCUSED artifact tabs (idempotent). */
1597
+ /**
1598
+ * Surfaces the reviewed chain's history artifacts inside the Activity tab. In the redesign
1599
+ * there are no per-artifact tabs — the artifacts ride the {@link ReviewArtifacts} input into
1600
+ * the rail's "Session artifacts" group, so this only needs to ensure the Activity tab is
1601
+ * shown (it carries the carryover artifacts). Idempotent; no-op without any review artifacts.
1602
+ */
1112
1603
  registerReviewArtifactTabs() {
1113
1604
  if (!this.surfaceTabs || this.reviewArtifacts.length === 0) {
1114
1605
  return;
1115
1606
  }
1116
- for (const artifact of this.reviewArtifacts) {
1117
- this.surfaceTabs.RegisterArtifactTab(artifact, false);
1118
- }
1607
+ // Show the Activity tab (where the carryover artifacts now live); RegisterArtifactTab
1608
+ // flips the gate on without opening any tab of its own.
1609
+ this.surfaceTabs.RegisterArtifactTab(this.reviewArtifacts[0], false);
1119
1610
  this.cdr.markForCheck();
1120
1611
  }
1121
1612
  /**
@@ -1154,17 +1645,37 @@ export class RealtimeSessionOverlayComponent {
1154
1645
  this.reviewWhiteboardTabRegistered = true;
1155
1646
  this.cdr.markForCheck();
1156
1647
  }
1648
+ /**
1649
+ * Registers the read-only review RECORDING tab (the time-aligned storage media player) — ONLY when
1650
+ * the reviewed session carried a `RecordingFileID`. Idempotent (re-registering the same key updates
1651
+ * it in place). The player resolves the authenticated audio itself from the `RecordingFileID` and
1652
+ * shows its own no-access / loading states; the click-to-seek transcript is usable immediately.
1653
+ * NOT focused — the channel surface / Activity rail keeps the default focus.
1654
+ */
1655
+ registerReviewRecordingTab() {
1656
+ if (!this._reviewData?.RecordingFileID || !this.recordingTpl) {
1657
+ return;
1658
+ }
1659
+ this.RegisterChannelTab({
1660
+ Key: 'Recording',
1661
+ Title: 'Recording',
1662
+ Icon: 'fa-solid fa-circle-play',
1663
+ Content: this.recordingTpl
1664
+ });
1665
+ this.reviewRecordingTabRegistered = true;
1666
+ this.cdr.markForCheck();
1667
+ }
1157
1668
  /**
1158
1669
  * A working delegation card's ✕ asked to cancel that delegated run — EXPLICIT user
1159
1670
  * intent (barge-in never cancels work, by deliberate policy). The service calls the
1160
1671
  * server cancel channel and flips the card to a failed "Cancelled by user" result.
1161
1672
  */
1162
1673
  OnCancelDelegation(callId) {
1163
- void this.voice.CancelDelegation(callId);
1674
+ void this.realtime.CancelDelegation(callId);
1164
1675
  }
1165
1676
  /** Minimizes the live call (it stays running) and asks the host to open the record. */
1166
1677
  requestNavigate(entityName, recordId) {
1167
- this.voice.SetMinimized(true);
1678
+ this.realtime.SetMinimized(true);
1168
1679
  this.NavigateRequest.emit({ EntityName: entityName, RecordID: recordId });
1169
1680
  }
1170
1681
  // ── Focus layout + pill ────────────────────────────────────────────────────
@@ -1172,11 +1683,12 @@ export class RealtimeSessionOverlayComponent {
1172
1683
  onChannelFocus(channel, focused) {
1173
1684
  this.ChannelFocusMode = focused;
1174
1685
  this.focusChannel = focused ? channel : null;
1175
- this.cdr.markForCheck();
1686
+ // channelFocus drives the resolver (hides panel + strip) — re-resolve (marks for check).
1687
+ this.recomputeUi();
1176
1688
  }
1177
- /** Focus pill: toggle the mic mute. */
1689
+ /** Focus pill: toggle the mic mute (routes through the overlay's single mute path). */
1178
1690
  OnFocusPillMute() {
1179
- this.FocusPillMuted = this.voice.ToggleMute();
1691
+ this.ToggleMute();
1180
1692
  }
1181
1693
  /** Focus pill: leave focus mode (show the thread column again). */
1182
1694
  OnFocusPillExit() {
@@ -1189,7 +1701,7 @@ export class RealtimeSessionOverlayComponent {
1189
1701
  }
1190
1702
  /** Focus pill: end the call (mirrors the controls row's End button). */
1191
1703
  async OnFocusPillEnd() {
1192
- await this.voice.EndVoiceSession();
1704
+ await this.realtime.EndRealtimeSession();
1193
1705
  this.Ended.emit();
1194
1706
  }
1195
1707
  /** Short status line for the focus pill (first person — the co-agent owns the work). */
@@ -1202,30 +1714,154 @@ export class RealtimeSessionOverlayComponent {
1202
1714
  default: return '';
1203
1715
  }
1204
1716
  }
1717
+ /**
1718
+ * The composer dock toggled the mic. Syncs the overlay's single {@link Muted} source (and
1719
+ * the focus pill) and surfaces the {@link MuteChanged} / {@link ControlInvoked} outputs.
1720
+ */
1721
+ OnComposerMuteChanged(muted) {
1722
+ if (muted !== this.Muted) {
1723
+ this.Muted = muted;
1724
+ this.FocusPillMuted = muted;
1725
+ this.cdr.markForCheck();
1726
+ }
1727
+ this.MuteChanged.emit(muted);
1728
+ this.ControlInvoked.emit('mute');
1729
+ }
1730
+ // ── Imperative API — drive the surface from a host via @ViewChild ────────────
1731
+ //
1732
+ // Each method maps to an existing internal handler/service so behaviour matches the
1733
+ // user clicking the corresponding control. See the package README's "Methods" table.
1734
+ /**
1735
+ * Override the chrome at runtime (equivalent to binding `[Chrome]`). Pass `'auto'` to
1736
+ * return to the size/intent-driven default.
1737
+ *
1738
+ * @param mode the chrome to force.
1739
+ * @example `voice.SetChrome('console');`
1740
+ */
1741
+ SetChrome(mode) {
1742
+ this.setUiInput('chrome', mode);
1743
+ }
1744
+ /**
1745
+ * Reveal the transcript programmatically — the imperative twin of the orb's "Show the
1746
+ * conversation" affordance. Raises the text disclosure level, turns captions on, and
1747
+ * emits {@link TextRevealed}.
1748
+ */
1749
+ RevealText() {
1750
+ this.OnTextReveal();
1751
+ }
1752
+ /**
1753
+ * Set the mic mute to an explicit state (no-op when already there). Drives the session
1754
+ * service, syncs every mute affordance, and emits {@link MuteChanged}.
1755
+ *
1756
+ * @param muted the desired muted state.
1757
+ */
1758
+ SetMuted(muted) {
1759
+ if (muted === this.Muted) {
1760
+ return;
1761
+ }
1762
+ this.ToggleMute();
1763
+ }
1764
+ /**
1765
+ * Toggle the mic mute. Drives the session service, syncs the overlay's {@link Muted}
1766
+ * source + the composer/focus-pill affordances, and emits {@link MuteChanged} +
1767
+ * {@link ControlInvoked}.
1768
+ */
1769
+ ToggleMute() {
1770
+ this.Muted = this.realtime.ToggleMute();
1771
+ this.FocusPillMuted = this.Muted;
1772
+ this.MuteChanged.emit(this.Muted);
1773
+ this.ControlInvoked.emit('mute');
1774
+ this.cdr.markForCheck();
1775
+ }
1776
+ /**
1777
+ * Set captions (the text-vs-orb preference) to an explicit state. Persisted per-user;
1778
+ * turning them on also raises the text disclosure level.
1779
+ *
1780
+ * @param on whether captions/text should be shown.
1781
+ */
1782
+ SetCaptions(on) {
1783
+ if (on === this.ShowCaptions) {
1784
+ return;
1785
+ }
1786
+ this.OnCaptionsToggled(on);
1787
+ }
1788
+ /** Toggle captions (the text-vs-orb preference). */
1789
+ ToggleCaptions() {
1790
+ this.OnCaptionsToggled(!this.ShowCaptions);
1791
+ }
1792
+ /**
1793
+ * Set the UX interface density (the gear's escape hatch). Applied + persisted immediately;
1794
+ * emits {@link DensityChanged}.
1795
+ *
1796
+ * @param density the density to apply.
1797
+ */
1798
+ SetDensity(density) {
1799
+ this.OnDensityChanged(density);
1800
+ }
1801
+ /**
1802
+ * Open the surface panel (the on-demand Details peek), optionally focusing a specific
1803
+ * channel's tab. Equivalent to the strip's Details control plus a channel reveal.
1804
+ *
1805
+ * @param channelId when supplied, the channel tab to reveal once the panel is shown.
1806
+ */
1807
+ OpenSurfacePanel(channelId) {
1808
+ if (!this.DetailsPeek) {
1809
+ this.DetailsPeek = true;
1810
+ this.ControlInvoked.emit('surface');
1811
+ this.recomputeUi();
1812
+ }
1813
+ setTimeout(() => {
1814
+ if (channelId) {
1815
+ this.surfaceTabs?.RevealChannel(channelId);
1816
+ }
1817
+ else {
1818
+ this.surfaceTabs?.FocusFirstTab();
1819
+ }
1820
+ });
1821
+ }
1822
+ /** Collapse the surface panel (hide the Details peek). No-op in review (always shown). */
1823
+ CollapseSurfacePanel() {
1824
+ if (this.IsReviewing || !this.DetailsPeek) {
1825
+ return;
1826
+ }
1827
+ this.DetailsPeek = false;
1828
+ this.ControlInvoked.emit('surface');
1829
+ this.recomputeUi();
1830
+ }
1831
+ /** Minimize the call (it stays live) — the imperative twin of the app-bar Minimize. */
1832
+ Minimize() {
1833
+ this.OnMinimize();
1834
+ }
1835
+ /** End the session (tear down the call) — the imperative twin of the End control. */
1836
+ EndSession() {
1837
+ void this.OnEndCall();
1838
+ }
1205
1839
  static ɵfac = function RealtimeSessionOverlayComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeSessionOverlayComponent)(); };
1206
1840
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeSessionOverlayComponent, selectors: [["mj-realtime-session-overlay"]], viewQuery: function RealtimeSessionOverlayComponent_Query(rf, ctx) { if (rf & 1) {
1207
- i0.ɵɵviewQuery(RealtimeSurfaceTabsComponent, 5)(RealtimeComposerComponent, 5)(_c0, 5);
1841
+ i0.ɵɵviewQuery(RealtimeSurfaceTabsComponent, 5)(RealtimeComposerComponent, 5)(_c0, 5)(_c1, 5);
1208
1842
  } if (rf & 2) {
1209
1843
  let _t;
1210
1844
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.surfaceTabsRef = _t.first);
1211
1845
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.composer = _t.first);
1212
1846
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.reviewBoardTpl = _t.first);
1847
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.recordingTpl = _t.first);
1213
1848
  } }, hostBindings: function RealtimeSessionOverlayComponent_HostBindings(rf, ctx) { if (rf & 1) {
1214
1849
  i0.ɵɵlistener("keydown", function RealtimeSessionOverlayComponent_keydown_HostBindingHandler($event) { return ctx.OnDocumentKeydown($event); }, i0.ɵɵresolveDocument);
1215
- } }, inputs: { Hidden: "Hidden", AgentName: "AgentName", CurrentUser: "CurrentUser", EnvironmentID: "EnvironmentID", ReviewData: "ReviewData" }, outputs: { Ended: "Ended", NavigateRequest: "NavigateRequest", StartLiveRequested: "StartLiveRequested", ReviewClosed: "ReviewClosed" }, decls: 4, vars: 3, consts: [["reviewBoardTpl", ""], ["role", "region", 1, "call-overlay", 3, "call-overlay--hidden", "call-overlay--review", "board-focus"], ["role", "region", 1, "call-overlay"], [1, "call-main"], [3, "OpenSessionRequested", "CaptionsToggled", "DevModeToggled", "DensityChanged", "MinimizeRequested", "PureAudioRequested", "EndRequested", "StartLiveRequested", "CloseRequested", "State", "AgentName", "ModelName", "DevMode", "SessionID", "ReviewMode", "ReviewStartedAt", "ReviewClosedAt", "ReviewCloseReason", "CaptionsOn", "ShowCaptionsControl", "ShowGear", "ShowEnd", "ShowMinimize", "ShowPureAudio", "Density"], ["role", "status", "aria-live", "polite", 1, "banner-live-note"], [1, "call-body"], [1, "call-connecting"], ["role", "status", "aria-live", "polite", 1, "hero"], [3, "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], ["role", "status", "aria-label", "Live call (board focus)", 1, "board-focus-pill"], ["aria-hidden", "true", 1, "fa-solid", "fa-comment-dots"], ["text", "Connecting to the live session\u2026", "size", "medium"], [1, "hero__stage"], ["aria-hidden", "true", 1, "hero__ring"], ["aria-hidden", "true", 1, "hero__ring", "hero__ring--2"], ["aria-hidden", "true", 1, "hero__ring", "hero__ring--3"], ["aria-hidden", "true", 1, "hero__orb"], ["aria-hidden", "true", 1, "hero__eq"], [1, "hero__name"], [1, "hero__sub"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-closed-captioning"], [3, "OpenRunRequested", "OpenArtifactRequested", "CancelRequested", "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [3, "OpenChanged", "CaptionsToggled", "DetailsToggled", "EndRequested", "Open", "CaptionsOn", "ShowDetails", "DetailsOn"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "call-resizer--active"], [1, "call-panel"], [3, "OpenRunRequested", "CollapsedChange", "WideChanged", "State", "DevMode", "CurrentUser", "EnvironmentID", "Fill"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "mousedown", "dblclick"], ["aria-hidden", "true", 1, "board-focus-pill__orb"], [1, "board-focus-pill__text"], [1, "board-focus-pill__name"], [1, "board-focus-pill__state"], ["type", "button", 1, "board-focus-pill__btn", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-solid"], ["type", "button", "title", "Show thread", "aria-label", "Show the conversation thread", 1, "board-focus-pill__btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-table-columns"], ["type", "button", "title", "End call", "aria-label", "End the call", 1, "board-focus-pill__btn", "board-focus-pill__btn--end", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone-slash"], [1, "review-board-host"], [3, "State", "AgentName", "ReadOnly"]], template: function RealtimeSessionOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1216
- i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Template, 12, 32, "div", 1);
1850
+ } }, inputs: { Hidden: "Hidden", AgentName: "AgentName", CurrentUser: "CurrentUser", EnvironmentID: "EnvironmentID", Chrome: "Chrome", ConsoleBreakpointPx: "ConsoleBreakpointPx", Compact: "Compact", AutoHideControls: "AutoHideControls", AllowTextReveal: "AllowTextReveal", ShowCaptionsControl: "ShowCaptionsControl", ShowDensityPicker: "ShowDensityPicker", ShowMinimize: "ShowMinimize", ShowEnd: "ShowEnd", ShowSurfacePanel: "ShowSurfacePanel", ShowChannels: "ShowChannels", ShowActivityRail: "ShowActivityRail", ShowDevLinks: "ShowDevLinks", AllowResize: "AllowResize", UiConfig: "UiConfig", ReviewData: "ReviewData" }, outputs: { Ended: "Ended", NavigateRequest: "NavigateRequest", StartLiveRequested: "StartLiveRequested", ReviewClosed: "ReviewClosed", Minimized: "Minimized", TextRevealed: "TextRevealed", ChromeChanged: "ChromeChanged", ConnectionStateChanged: "ConnectionStateChanged", MuteChanged: "MuteChanged", DensityChanged: "DensityChanged", SurfacePanelResized: "SurfacePanelResized", ControlInvoked: "ControlInvoked" }, features: [i0.ɵɵInheritDefinitionFeature], decls: 6, vars: 3, consts: [["reviewBoardTpl", ""], ["recordingTpl", ""], ["role", "region", 1, "call-overlay", 3, "call-overlay--hidden", "call-overlay--review", "board-focus"], ["role", "region", 1, "call-overlay"], [1, "call-main"], [3, "OpenSessionRequested", "DevModeToggled", "DensityChanged", "MinimizeRequested", "StartLiveRequested", "CloseRequested", "State", "Compact", "AgentName", "ModelName", "DevMode", "SessionID", "ReviewMode", "ReviewStartedAt", "ReviewClosedAt", "ReviewCloseReason", "ShowGear", "ShowDensityPicker", "ShowMinimize", "Density"], ["role", "status", "aria-live", "polite", 1, "banner-live-note"], [1, "call-body"], [1, "call-connecting"], ["role", "status", "aria-live", "polite", 1, "hero"], [3, "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], ["role", "status", "aria-label", "Live call (board focus)", 1, "board-focus-pill"], ["aria-hidden", "true", 1, "fa-solid", "fa-comment-dots"], ["text", "Connecting to the live session\u2026", "size", "medium"], [1, "hero__stage"], ["aria-hidden", "true", 1, "hero__ring"], ["aria-hidden", "true", 1, "hero__ring", "hero__ring--2"], ["aria-hidden", "true", 1, "hero__ring", "hero__ring--3"], ["aria-hidden", "true", 1, "hero__orb"], ["aria-hidden", "true", 1, "hero__eq"], [1, "hero__name"], [1, "hero__sub"], ["type", "button", "title", "Show the conversation as text", 1, "hero__reveal", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-closed-captioning"], [3, "OpenRunRequested", "OpenArtifactRequested", "CancelRequested", "State", "AgentName", "UserName", "ShowCaptions", "DevMode"], [3, "OpenChanged", "CaptionsToggled", "DetailsToggled", "MuteChanged", "EndRequested", "Open", "Compact", "CaptionsOn", "IsMuted", "ShowDetails", "DetailsOn"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "call-resizer--active"], [1, "call-panel"], [3, "OpenRunRequested", "CollapsedChange", "WideChanged", "State", "DevMode", "CurrentUser", "EnvironmentID", "ShowActivityTab", "ExtraArtifacts", "Fill"], ["role", "separator", "aria-orientation", "vertical", "aria-label", "Resize the surface panel (double-click to reset)", "title", "Drag to resize \u2022 double-click to reset", 1, "call-resizer", 3, "mousedown", "dblclick"], ["aria-hidden", "true", 1, "board-focus-pill__orb"], [1, "board-focus-pill__text"], [1, "board-focus-pill__name"], [1, "board-focus-pill__state"], ["type", "button", 1, "board-focus-pill__btn", 3, "click", "title"], ["aria-hidden", "true", 1, "fa-solid"], ["type", "button", "title", "Show thread", "aria-label", "Show the conversation thread", 1, "board-focus-pill__btn", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-table-columns"], ["type", "button", "title", "End call", "aria-label", "End the call", 1, "board-focus-pill__btn", "board-focus-pill__btn--end", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-phone-slash"], [1, "review-board-host"], [3, "State", "AgentName", "ReadOnly"], [1, "review-recording-host"], [3, "FileID", "Transcript", "Provider"]], template: function RealtimeSessionOverlayComponent_Template(rf, ctx) { if (rf & 1) {
1851
+ i0.ɵɵconditionalCreate(0, RealtimeSessionOverlayComponent_Conditional_0_Template, 12, 30, "div", 2);
1217
1852
  i0.ɵɵpipe(1, "async");
1218
- i0.ɵɵtemplate(2, RealtimeSessionOverlayComponent_ng_template_2_Template, 1, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor);
1853
+ i0.ɵɵtemplate(2, RealtimeSessionOverlayComponent_ng_template_2_Template, 1, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor)(4, RealtimeSessionOverlayComponent_ng_template_4_Template, 2, 3, "ng-template", null, 1, i0.ɵɵtemplateRefExtractor);
1219
1854
  } if (rf & 2) {
1220
- let tmp_1_0;
1221
- i0.ɵɵconditional((tmp_1_0 = i0.ɵɵpipeBind1(1, 1, ctx.ConnectionState$)) ? 0 : -1, tmp_1_0);
1855
+ let tmp_2_0;
1856
+ i0.ɵɵconditional((tmp_2_0 = i0.ɵɵpipeBind1(1, 1, ctx.ConnectionState$)) ? 0 : -1, tmp_2_0);
1222
1857
  } }, dependencies: [CommonModule,
1223
1858
  SharedGenericModule, i1.LoadingComponent, RealtimeAgentBannerComponent,
1224
1859
  RealtimeSessionThreadComponent,
1225
1860
  RealtimeChannelStripComponent,
1226
1861
  RealtimeComposerComponent,
1227
1862
  RealtimeSurfaceTabsComponent,
1228
- RealtimeWhiteboardBoardComponent, i2.AsyncPipe], styles: ["[_nghost-%COMP%] {\n display: contents;\n}\n\n\n\n\n\n\n.call-overlay[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n\n\n\n.call-overlay--hidden[_ngcontent-%COMP%] {\n display: none;\n}\n\n\n\n\n\n.call-main[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.call-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 6px;\n}\n.call-body[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n\n\n\n\n\n.call-body--hero[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: _ngcontent-%COMP%_hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes _ngcontent-%COMP%_hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n.hero__stage[_ngcontent-%COMP%] {\n position: relative;\n width: 220px;\n height: 220px;\n display: grid;\n place-items: center;\n margin-bottom: 4px;\n}\n\n\n.hero__orb[_ngcontent-%COMP%] {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n position: relative;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.hero__orb[data-state='speaking'][_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_hero-orb-bob 0.9s ease-in-out infinite;\n}\n.hero__orb[data-state='listening'][_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.hero__orb[data-state='thinking'][_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 40px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n@keyframes _ngcontent-%COMP%_hero-orb-bob {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n}\n\n\n.hero__ring[_ngcontent-%COMP%] {\n position: absolute;\n width: 120px;\n height: 120px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 55%, transparent);\n opacity: 0;\n animation: _ngcontent-%COMP%_hero-wave 2.7s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;\n}\n.hero__ring--2[_ngcontent-%COMP%] { animation-delay: 0.9s; }\n.hero__ring--3[_ngcontent-%COMP%] { animation-delay: 1.8s; }\n@keyframes _ngcontent-%COMP%_hero-wave {\n 0% { transform: scale(1); opacity: 0.5; }\n 70% { opacity: 0.1; }\n 100% { transform: scale(1.85); opacity: 0; }\n}\n\n\n.hero__eq[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(1) { animation-delay: 0s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(9) { animation-delay: 0.03s; }\n@keyframes _ngcontent-%COMP%_hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n.hero__name[_ngcontent-%COMP%] {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub[_ngcontent-%COMP%] {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n\n\n\n\n\n\n\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__orb[_ngcontent-%COMP%] {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.18));\n transition: transform 70ms linear;\n}\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__ring[_ngcontent-%COMP%] {\n opacity: calc(var(--voice-out, 0) * 0.55);\n animation-duration: 2.1s;\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__orb[_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 45%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__ring[_ngcontent-%COMP%] {\n border-color: color-mix(in srgb, var(--mj-status-success) 55%, transparent);\n}\n\n\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n background: var(--mj-status-success);\n}\n\n\n\n.hero__reveal[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n\n\n.banner-live-note[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: _ngcontent-%COMP%_overlay-note-in 220ms ease;\n}\n.banner-live-note[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes _ngcontent-%COMP%_overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n\n\n\n\n.call-overlay.board-focus[_ngcontent-%COMP%] .call-main[_ngcontent-%COMP%] {\n display: none;\n}\n.call-overlay.board-focus[_ngcontent-%COMP%] mj-realtime-surface-tabs[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n\n\n.board-focus-pill[_ngcontent-%COMP%] {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: _ngcontent-%COMP%_overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb[_ngcontent-%COMP%] {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'][_ngcontent-%COMP%], \n.board-focus-pill__orb[data-state='listening'][_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'][_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes _ngcontent-%COMP%_board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state[_ngcontent-%COMP%] {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n\n\n\n\n.call-overlay--review[_ngcontent-%COMP%] {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n\n\n\n.review-board-host[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n\n\n.call-resizer[_ngcontent-%COMP%] {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer[_ngcontent-%COMP%]:hover, \n.call-resizer--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n\n\n\n\n\n.call-panel[_ngcontent-%COMP%] {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n \n\n\n animation: _ngcontent-%COMP%_panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes _ngcontent-%COMP%_panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel[_ngcontent-%COMP%] > *[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n\n\n.call-overlay.board-focus[_ngcontent-%COMP%] .call-panel[_ngcontent-%COMP%] {\n flex: 1;\n}"] });
1863
+ RealtimeWhiteboardBoardComponent,
1864
+ MJStorageMediaPlayerComponent, i2.AsyncPipe], styles: ["[_nghost-%COMP%] {\n display: contents;\n}\n\n\n\n\n\n\n.call-overlay[_ngcontent-%COMP%] {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n\n\n\n.call-overlay--hidden[_ngcontent-%COMP%] {\n display: none;\n}\n\n\n\n\n\n.call-main[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.call-body[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body[_ngcontent-%COMP%]::-webkit-scrollbar {\n width: 6px;\n}\n.call-body[_ngcontent-%COMP%]::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n\n\n\n\n\n.call-body--hero[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: _ngcontent-%COMP%_hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes _ngcontent-%COMP%_hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n.hero__stage[_ngcontent-%COMP%] {\n position: relative;\n width: 220px;\n height: 220px;\n display: grid;\n place-items: center;\n margin-bottom: 4px;\n}\n\n\n.hero__orb[_ngcontent-%COMP%] {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n position: relative;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.hero__orb[data-state='speaking'][_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_hero-orb-bob 0.9s ease-in-out infinite;\n}\n.hero__orb[data-state='listening'][_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.hero__orb[data-state='thinking'][_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 40px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n@keyframes _ngcontent-%COMP%_hero-orb-bob {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n}\n\n\n.hero__ring[_ngcontent-%COMP%] {\n position: absolute;\n width: 120px;\n height: 120px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 55%, transparent);\n opacity: 0;\n animation: _ngcontent-%COMP%_hero-wave 2.7s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;\n}\n.hero__ring--2[_ngcontent-%COMP%] { animation-delay: 0.9s; }\n.hero__ring--3[_ngcontent-%COMP%] { animation-delay: 1.8s; }\n@keyframes _ngcontent-%COMP%_hero-wave {\n 0% { transform: scale(1); opacity: 0.5; }\n 70% { opacity: 0.1; }\n 100% { transform: scale(1.85); opacity: 0; }\n}\n\n\n.hero__eq[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(1) { animation-delay: 0s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(9) { animation-delay: 0.03s; }\n@keyframes _ngcontent-%COMP%_hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n.hero__name[_ngcontent-%COMP%] {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub[_ngcontent-%COMP%] {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n\n\n\n\n\n\n\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__orb[_ngcontent-%COMP%] {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.18));\n transition: transform 70ms linear;\n}\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__ring[_ngcontent-%COMP%] {\n opacity: calc(var(--voice-out, 0) * 0.55);\n animation-duration: 2.1s;\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__orb[_ngcontent-%COMP%] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 45%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__ring[_ngcontent-%COMP%] {\n border-color: color-mix(in srgb, var(--mj-status-success) 55%, transparent);\n}\n\n\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'][_ngcontent-%COMP%] .hero__eq[_ngcontent-%COMP%] span[_ngcontent-%COMP%] {\n background: var(--mj-status-success);\n}\n\n\n\n.hero__reveal[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n\n\n.banner-live-note[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: _ngcontent-%COMP%_overlay-note-in 220ms ease;\n}\n.banner-live-note[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes _ngcontent-%COMP%_overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n\n\n\n\n.call-overlay.board-focus[_ngcontent-%COMP%] .call-main[_ngcontent-%COMP%] {\n display: none;\n}\n.call-overlay.board-focus[_ngcontent-%COMP%] mj-realtime-surface-tabs[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n}\n\n\n\n.board-focus-pill[_ngcontent-%COMP%] {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: _ngcontent-%COMP%_overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb[_ngcontent-%COMP%] {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'][_ngcontent-%COMP%], \n.board-focus-pill__orb[data-state='listening'][_ngcontent-%COMP%] {\n animation: _ngcontent-%COMP%_board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'][_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes _ngcontent-%COMP%_board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name[_ngcontent-%COMP%] {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state[_ngcontent-%COMP%] {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn[_ngcontent-%COMP%] {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end[_ngcontent-%COMP%] {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n\n\n\n\n.call-overlay--review[_ngcontent-%COMP%] {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n\n\n\n.review-board-host[_ngcontent-%COMP%] {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n\n\n\n.review-recording-host[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n.review-recording-host[_ngcontent-%COMP%] mj-realtime-evidence-playback[_ngcontent-%COMP%] {\n flex: 1;\n min-height: 0;\n}\n\n\n\n\n\n\n.call-resizer[_ngcontent-%COMP%] {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer[_ngcontent-%COMP%]:hover, \n.call-resizer--active[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n\n\n\n\n\n.call-panel[_ngcontent-%COMP%] {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n \n\n\n animation: _ngcontent-%COMP%_panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes _ngcontent-%COMP%_panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel[_ngcontent-%COMP%] > *[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n\n\n.call-overlay.board-focus[_ngcontent-%COMP%] .call-panel[_ngcontent-%COMP%] {\n flex: 1;\n}"] });
1229
1865
  }
1230
1866
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeSessionOverlayComponent, [{
1231
1867
  type: Component,
@@ -1237,8 +1873,9 @@ export class RealtimeSessionOverlayComponent {
1237
1873
  RealtimeChannelStripComponent,
1238
1874
  RealtimeComposerComponent,
1239
1875
  RealtimeSurfaceTabsComponent,
1240
- RealtimeWhiteboardBoardComponent
1241
- ], template: "@if (ConnectionState$ | async; as state) {\n <div class=\"call-overlay\"\n [class.call-overlay--hidden]=\"Hidden\"\n [class.call-overlay--review]=\"IsReviewing\"\n [class.board-focus]=\"ChannelFocusMode\"\n role=\"region\" [attr.aria-label]=\"IsReviewing ? 'Past session review' : 'Live voice session'\">\n\n <!-- PROGRESSIVE DISCLOSURE (the console that grows with you \u2014 Redesign A Progressive):\n level 0 is PURE AUDIO (the hero orb owns the main column; no thread, no composer;\n the strip's Details control peeks at the panels on demand); level 1 reveals the\n caption thread + captions control + type-to-type hint; level 2+ docks the composer\n and keeps the surface panel. CONTENT NEVER FLIPS THE CONSOLE OPEN: finished\n artifacts land as glowing unfocused tabs; the ONE auto-reveal is a channel's\n first agent activity (e.g. the first whiteboard write), which opens the panel as\n a peek with that tab focused \u2014 the left column stays exactly as it was.\n REVIEW always renders the full console. -->\n <div class=\"call-main\">\n <!-- The UNIFIED APP-BAR: identity + state + every session action (Redesign A). -->\n <mj-realtime-agent-banner\n [State]=\"state\"\n [AgentName]=\"AgentName\"\n [ModelName]=\"IsReviewing ? null : (ModelName$ | async)\"\n [DevMode]=\"DevMode\"\n [SessionID]=\"IsReviewing ? ReviewSessionID : SessionID\"\n [ReviewMode]=\"IsReviewing\"\n [ReviewStartedAt]=\"ReviewStartedAt\"\n [ReviewClosedAt]=\"ReviewClosedAt\"\n [ReviewCloseReason]=\"ReviewCloseReason\"\n [CaptionsOn]=\"ShowCaptions\"\n [ShowCaptionsControl]=\"!IsReviewing\"\n [ShowGear]=\"!IsReviewing && Disclosure.ShowGear\"\n [ShowEnd]=\"!IsReviewing && Disclosure.ShowComposer\"\n [ShowMinimize]=\"!IsReviewing\"\n [ShowPureAudio]=\"!IsReviewing && Disclosure.SessionLevel > 0\"\n [Density]=\"Disclosure.Milestones.Density\"\n (OpenSessionRequested)=\"OnOpenSessionRequested($event)\"\n (CaptionsToggled)=\"OnCaptionsToggled($event)\"\n (DevModeToggled)=\"OnDevModeToggled($event)\"\n (DensityChanged)=\"OnDensityChanged($event)\"\n (MinimizeRequested)=\"OnMinimize()\"\n (PureAudioRequested)=\"OnPureAudio()\"\n (EndRequested)=\"OnEndCall()\"\n (StartLiveRequested)=\"OnStartLive()\"\n (CloseRequested)=\"OnReviewClose()\">\n </mj-realtime-agent-banner>\n\n <!-- Ephemeral narration with no active working card to anchor to \u2192 under the banner -->\n @if (!IsReviewing && State.Narration && !State.ActiveCallId && Disclosure.ShowThread) {\n <div class=\"banner-live-note\" role=\"status\" aria-live=\"polite\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n {{ State.Narration }}\n </div>\n }\n\n <div class=\"call-body\" [class.call-body--hero]=\"ShowHero && state !== 'connecting'\">\n @if (!IsReviewing && state === 'connecting') {\n <div class=\"call-connecting\">\n <mj-loading text=\"Connecting to the live session\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (ShowHero) {\n <!-- PURE-AUDIO HERO (level 0): the orb IS the interface \u2014 it breathes with the\n audio inside expanding sound-wave rings; nothing to read, nothing to configure. -->\n <div class=\"hero\" [attr.data-state]=\"state\" role=\"status\" aria-live=\"polite\">\n <div class=\"hero__stage\">\n <span class=\"hero__ring\" aria-hidden=\"true\"></span>\n <span class=\"hero__ring hero__ring--2\" aria-hidden=\"true\"></span>\n <span class=\"hero__ring hero__ring--3\" aria-hidden=\"true\"></span>\n <div class=\"hero__orb\" [attr.data-state]=\"HeroOrbState(state)\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"hero__eq\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n <div class=\"hero__name\">{{ AgentName }}</div>\n <div class=\"hero__sub\">{{ HeroStateLabel(state) }}</div>\n <button type=\"button\" class=\"hero__reveal\" (click)=\"OnTextReveal()\"\n title=\"Show the conversation as text\">\n <i class=\"fa-solid fa-closed-captioning\" aria-hidden=\"true\"></i>\n Show the conversation\n </button>\n </div>\n } @else {\n <mj-realtime-session-thread\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [UserName]=\"CurrentUser?.Name || 'You'\"\n [ShowCaptions]=\"IsReviewing ? true : ShowCaptions\"\n [DevMode]=\"DevMode\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (OpenArtifactRequested)=\"OnOpenArtifactRequested($event)\"\n (CancelRequested)=\"OnCancelDelegation($event)\">\n </mj-realtime-session-thread>\n }\n </div>\n\n @if (!IsReviewing) {\n <!-- Active channels (earned with the console at level 2+) -->\n @if (Disclosure.ShowComposer) {\n <mj-realtime-channel-strip></mj-realtime-channel-strip>\n }\n\n <!-- The BOTTOM DOCK: phone-call strip \u21C4 fused minis+composer \u2014 the user's own\n two-way toggle (Type opens, hide closes), never level-forced. -->\n <mj-realtime-composer\n [Open]=\"ComposerOpen\"\n (OpenChanged)=\"OnComposerOpenChanged($event)\"\n [CaptionsOn]=\"ShowCaptions\"\n [ShowDetails]=\"ShowDetailsControl\"\n [DetailsOn]=\"DetailsPeek\"\n (CaptionsToggled)=\"OnCaptionsToggled($event)\"\n (DetailsToggled)=\"OnDetailsToggled()\"\n (EndRequested)=\"OnEndCall()\">\n </mj-realtime-composer>\n }\n </div>\n\n <!-- RIGHT: TABBED SURFACE PANEL \u2014 disclosure-gated: it EXISTS once earned (level 2+,\n which the first delegation forces), peeked on demand via the strip's Details\n control, or always in review. Slides in from the right when it arrives \u2014 the\n panel's arrival IS the progress cue. -->\n @if (ShowPanelArea) {\n <!-- Resize handle (hidden while the panel is collapsed or a channel holds focus). -->\n @if (!PanelResizeDisabled) {\n <div class=\"call-resizer\"\n [class.call-resizer--active]=\"IsPanelResizing\"\n role=\"separator\" aria-orientation=\"vertical\"\n aria-label=\"Resize the surface panel (double-click to reset)\"\n title=\"Drag to resize \u2022 double-click to reset\"\n (mousedown)=\"OnPanelResizeStart($event)\"\n (dblclick)=\"OnPanelResizeReset()\"></div>\n }\n\n <div class=\"call-panel\" [style.width.px]=\"ChannelFocusMode ? null : PanelAreaSize\">\n <mj-realtime-surface-tabs\n [State]=\"State\"\n [DevMode]=\"DevMode\"\n [CurrentUser]=\"CurrentUser\"\n [EnvironmentID]=\"EnvironmentID\"\n [Fill]=\"ChannelFocusMode\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (CollapsedChange)=\"OnPanelCollapsedChange($event)\"\n (WideChanged)=\"OnPanelWideChanged($event)\">\n </mj-realtime-surface-tabs>\n </div>\n }\n\n <!-- FOCUS floating call pill: the main column is hidden, so the live call's\n identity + state + mute / show-thread / end controls ride over the surface. -->\n @if (!IsReviewing && ChannelFocusMode) {\n <div class=\"board-focus-pill\" role=\"status\" aria-label=\"Live call (board focus)\">\n <span class=\"board-focus-pill__orb\" [attr.data-state]=\"state\" aria-hidden=\"true\"></span>\n <span class=\"board-focus-pill__text\">\n <span class=\"board-focus-pill__name\">Voice for {{ AgentName }}</span>\n @if (FocusStateLabel(state); as label) {\n <span class=\"board-focus-pill__state\">{{ label }}</span>\n }\n </span>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n [class.board-focus-pill__btn--active]=\"FocusPillMuted\"\n (click)=\"OnFocusPillMute()\"\n [title]=\"FocusPillMuted ? 'Unmute' : 'Mute'\"\n [attr.aria-label]=\"FocusPillMuted ? 'Unmute microphone' : 'Mute microphone'\">\n <i class=\"fa-solid\" [class.fa-microphone]=\"!FocusPillMuted\" [class.fa-microphone-slash]=\"FocusPillMuted\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n (click)=\"OnFocusPillExit()\"\n title=\"Show thread\" aria-label=\"Show the conversation thread\">\n <i class=\"fa-solid fa-table-columns\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"board-focus-pill__btn board-focus-pill__btn--end\"\n (click)=\"OnFocusPillEnd()\"\n title=\"End call\" aria-label=\"End the call\">\n <i class=\"fa-solid fa-phone-slash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n </div>\n}\n\n<!-- SESSION REVIEW whiteboard pane body: the reviewed session's saved board, rehydrated\n locally (WhiteboardState.FromJSON) and rendered READ-ONLY (pan/zoom only). Registered\n as a template-based channel tab ONLY when a parseable Whiteboard state exists. -->\n<ng-template #reviewBoardTpl>\n @if (ReviewWhiteboard; as board) {\n <!-- The board host positions itself absolutely \u2014 give it a filling, positioned frame. -->\n <div class=\"review-board-host\">\n <mj-realtime-whiteboard\n [State]=\"board\"\n [AgentName]=\"AgentName\"\n [ReadOnly]=\"true\">\n </mj-realtime-whiteboard>\n </div>\n }\n</ng-template>\n", styles: [":host {\n display: contents;\n}\n\n/* Fills the conversation panel IN PLACE: the chat area host provides the positioned\n context (position:relative on .chat-area) and this covers it edge to edge \u2014 header,\n thread and composer included. Opaque-ish page surface with a slight blur so the\n conversation underneath reads as \"paused\", not as an app-wide modal. */\n.call-overlay {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n/* Minimized: hidden (NOT destroyed) so the shell's session/view state survives while\n the host shows its floating \"on call\" pill. The audio session is untouched. */\n.call-overlay--hidden {\n display: none;\n}\n\n/* MAIN column: banner (top) \u2192 scrolling thread \u2192 channel strip \u2192 composer \u2192 controls.\n The 420px floor keeps the call column readable while the panel is dragged wide\n (the drag clamp's 70% cap is the other guard). */\n.call-main {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.call-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body::-webkit-scrollbar {\n width: 6px;\n}\n.call-body::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n/* ---------- PURE-AUDIO HERO (disclosure level 0) ----------\n The orb IS the interface: it breathes with the audio inside expanding sound-wave\n rings, with a small voice equalizer underneath. Nothing to read, nothing to\n configure \u2014 text, panels and the composer are revealed later (or peeked on demand). */\n.call-body--hero {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n.hero__stage {\n position: relative;\n width: 220px;\n height: 220px;\n display: grid;\n place-items: center;\n margin-bottom: 4px;\n}\n/* The hero orb mirrors the app-bar orb's visual language at stage scale. */\n.hero__orb {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n position: relative;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.hero__orb[data-state='speaking'] {\n animation: hero-orb-bob 0.9s ease-in-out infinite;\n}\n.hero__orb[data-state='listening'] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.hero__orb[data-state='thinking'] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 40px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n@keyframes hero-orb-bob {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n}\n/* Expanding sound-wave rings (staggered). */\n.hero__ring {\n position: absolute;\n width: 120px;\n height: 120px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 55%, transparent);\n opacity: 0;\n animation: hero-wave 2.7s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;\n}\n.hero__ring--2 { animation-delay: 0.9s; }\n.hero__ring--3 { animation-delay: 1.8s; }\n@keyframes hero-wave {\n 0% { transform: scale(1); opacity: 0.5; }\n 70% { opacity: 0.1; }\n 100% { transform: scale(1.85); opacity: 0; }\n}\n/* Voice equalizer: lively while the agent speaks, near-still otherwise. */\n.hero__eq {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq span {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'] .hero__eq span {\n animation: hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq span:nth-child(1) { animation-delay: 0s; }\n.hero__eq span:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq span:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq span:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq span:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq span:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq span:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq span:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq span:nth-child(9) { animation-delay: 0.03s; }\n@keyframes hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n.hero__name {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n/* ---------- AUDIO-REACTIVE MODE (data-audio-live, set by the rAF meter loop) ----------\n With real audio metering the orb stops acting and starts REACTING: scale + glow follow\n the smoothed output envelope (--voice-out) like a speaker cone, the EQ renders the true\n spectrum (--eq-1..9), and [data-voice-dir] colors the visuals by who is speaking\n (agent = brand, user = green). Without metering (driver attached no meters) the\n attribute stays 'false' and the turn-state keyframe animations above stay in charge. */\n.call-overlay[data-audio-live='true'] .hero__orb {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.18));\n transition: transform 70ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__ring {\n opacity: calc(var(--voice-out, 0) * 0.55);\n animation-duration: 2.1s;\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__orb {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 45%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__ring {\n border-color: color-mix(in srgb, var(--mj-status-success) 55%, transparent);\n}\n/* True-spectrum EQ: per-bar heights from the meter's bins. */\n.call-overlay[data-audio-live='true'] .hero__eq span {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__eq span {\n background: var(--mj-status-success);\n}\n\n/* The quiet first reveal: show the conversation as text (raises disclosure to level 1). */\n.hero__reveal {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal i {\n font-size: 11px;\n}\n\n/* Ephemeral narration shown under the banner when no working card anchors it */\n.banner-live-note {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: overlay-note-in 220ms ease;\n}\n.banner-live-note i {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ---------- FOCUS-BOARD mode (whiteboard \"Focus board\" toggle) ----------\n The main call column collapses away entirely and the surface panel (whiteboard tab)\n fills the overlay; a compact floating call pill keeps the live call reachable. */\n.call-overlay.board-focus .call-main {\n display: none;\n}\n.call-overlay.board-focus mj-realtime-surface-tabs {\n flex: 1;\n min-width: 0;\n}\n\n/* Floating call pill: orb + state + mute / show-thread / end (per the whiteboard mockup). */\n.board-focus-pill {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'],\n.board-focus-pill__orb[data-state='listening'] {\n animation: board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n/* ---------- Session review mode ---------- */\n/* Subtle archival tint so a glance says \"this is history, not a live call\". */\n.call-overlay--review {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n/* The review whiteboard pane body: the board component positions itself absolutely\n (inset: 0), so its template-based pane needs a filling, positioned frame. */\n.review-board-host {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ---------- flex layout (main column | resize handle | surface panel) ---------- */\n/* Token-styled resize handle: invisible until hover, brand-tinted while interacting.\n 7px hit target, col-resize cursor; removed (not rendered) while collapsed / focus. */\n.call-resizer {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer:hover,\n.call-resizer--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n/* The surface panel: a plain fixed-width flex item (inline width set by the overlay;\n collapsed = the slim strip width). flex-shrink lets it yield to the call column's\n min-width on tight overlays; overflow hidden keeps the flex height chain intact so\n the panel's content scrolls internally. */\n.call-panel {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n /* Disclosure-gated: the panel is CREATED when earned/peeked \u2014 its arrival slides in\n from the right so the reveal doubles as the progress cue. */\n animation: panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel > * {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n/* Board-focus fill: the panel owns the whole overlay (the main column is hidden). */\n.call-overlay.board-focus .call-panel {\n flex: 1;\n}\n"] }]
1876
+ RealtimeWhiteboardBoardComponent,
1877
+ MJStorageMediaPlayerComponent
1878
+ ], template: "@if (ConnectionState$ | async; as state) {\n <div class=\"call-overlay\"\n [class.call-overlay--hidden]=\"Hidden\"\n [class.call-overlay--review]=\"IsReviewing\"\n [class.board-focus]=\"ChannelFocusMode\"\n role=\"region\" [attr.aria-label]=\"IsReviewing ? 'Past session review' : 'Live voice session'\">\n\n <!-- PROGRESSIVE DISCLOSURE (the console that grows with you \u2014 Redesign A Progressive):\n level 0 is PURE AUDIO (the hero orb owns the main column; no thread, no composer;\n the strip's Details control peeks at the panels on demand); level 1 reveals the\n caption thread + captions control + type-to-type hint; level 2+ docks the composer\n and keeps the surface panel. CONTENT NEVER FLIPS THE CONSOLE OPEN: finished\n artifacts land as glowing unfocused tabs; the ONE auto-reveal is a channel's\n first agent activity (e.g. the first whiteboard write), which opens the panel as\n a peek with that tab focused \u2014 the left column stays exactly as it was.\n REVIEW always renders the full console. -->\n <div class=\"call-main\">\n <!-- The UNIFIED APP-BAR: identity + state + every session action (Redesign A). -->\n <mj-realtime-agent-banner\n [State]=\"state\"\n [Compact]=\"!IsReviewing && Ui.compact && Ui.chrome === 'orb'\"\n [AgentName]=\"AgentName\"\n [ModelName]=\"IsReviewing ? null : (ModelName$ | async)\"\n [DevMode]=\"DevMode\"\n [SessionID]=\"IsReviewing ? ReviewSessionID : SessionID\"\n [ReviewMode]=\"IsReviewing\"\n [ReviewStartedAt]=\"ReviewStartedAt\"\n [ReviewClosedAt]=\"ReviewClosedAt\"\n [ReviewCloseReason]=\"ReviewCloseReason\"\n [ShowGear]=\"!IsReviewing && Ui.showGear\"\n [ShowDensityPicker]=\"Ui.showDensityPicker\"\n [ShowMinimize]=\"!IsReviewing && Ui.showMinimize\"\n [Density]=\"Disclosure.Milestones.Density\"\n (OpenSessionRequested)=\"OnOpenSessionRequested($event)\"\n (DevModeToggled)=\"OnDevModeToggled($event)\"\n (DensityChanged)=\"OnDensityChanged($event)\"\n (MinimizeRequested)=\"OnMinimize()\"\n (StartLiveRequested)=\"OnStartLive()\"\n (CloseRequested)=\"OnReviewClose()\">\n </mj-realtime-agent-banner>\n\n <!-- Ephemeral narration with no active working card to anchor to \u2192 under the banner -->\n @if (!IsReviewing && State.Narration && !State.ActiveCallId && Disclosure.ShowThread) {\n <div class=\"banner-live-note\" role=\"status\" aria-live=\"polite\">\n <i class=\"fa-solid fa-comment-dots\" aria-hidden=\"true\"></i>\n {{ State.Narration }}\n </div>\n }\n\n <div class=\"call-body\" [class.call-body--hero]=\"ShowHero && state !== 'connecting'\">\n @if (!IsReviewing && state === 'connecting') {\n <div class=\"call-connecting\">\n <mj-loading text=\"Connecting to the live session\u2026\" size=\"medium\"></mj-loading>\n </div>\n } @else if (ShowHero) {\n <!-- PURE-AUDIO HERO (level 0): the orb IS the interface \u2014 it breathes with the\n audio inside expanding sound-wave rings; nothing to read, nothing to configure. -->\n <div class=\"hero\" [attr.data-state]=\"state\" role=\"status\" aria-live=\"polite\">\n <div class=\"hero__stage\">\n <span class=\"hero__ring\" aria-hidden=\"true\"></span>\n <span class=\"hero__ring hero__ring--2\" aria-hidden=\"true\"></span>\n <span class=\"hero__ring hero__ring--3\" aria-hidden=\"true\"></span>\n <div class=\"hero__orb\" [attr.data-state]=\"HeroOrbState(state)\" aria-hidden=\"true\"></div>\n </div>\n <div class=\"hero__eq\" aria-hidden=\"true\">\n <span></span><span></span><span></span><span></span><span></span>\n <span></span><span></span><span></span><span></span>\n </div>\n <div class=\"hero__name\">{{ AgentName }}</div>\n <div class=\"hero__sub\">{{ HeroStateLabel(state) }}</div>\n <button type=\"button\" class=\"hero__reveal\" (click)=\"OnTextReveal()\"\n title=\"Show the conversation as text\">\n <i class=\"fa-solid fa-closed-captioning\" aria-hidden=\"true\"></i>\n Show the conversation\n </button>\n </div>\n } @else {\n <mj-realtime-session-thread\n [State]=\"State\"\n [AgentName]=\"AgentName\"\n [UserName]=\"CurrentUser?.Name || 'You'\"\n [ShowCaptions]=\"IsReviewing ? true : ShowCaptions\"\n [DevMode]=\"DevMode\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (OpenArtifactRequested)=\"OnOpenArtifactRequested($event)\"\n (CancelRequested)=\"OnCancelDelegation($event)\">\n </mj-realtime-session-thread>\n }\n </div>\n\n @if (!IsReviewing) {\n <!-- Active channels (earned with the console at level 2+, host-gated via [ShowChannels]).\n The strip component self-hides when there are no active channels. -->\n @if (ChannelStripVisible) {\n <mj-realtime-channel-strip></mj-realtime-channel-strip>\n }\n\n <!-- The BOTTOM DOCK: phone-call strip \u21C4 fused minis+composer \u2014 the user's own\n two-way toggle (Type opens, hide closes), never level-forced. -->\n <mj-realtime-composer\n [Open]=\"ComposerOpen\"\n [Compact]=\"Ui.compact && Ui.chrome === 'orb'\"\n (OpenChanged)=\"OnComposerOpenChanged($event)\"\n [CaptionsOn]=\"ShowCaptions\"\n [IsMuted]=\"Muted\"\n [ShowDetails]=\"ShowDetailsControl\"\n [DetailsOn]=\"DetailsPeek\"\n (CaptionsToggled)=\"OnCaptionsToggled($event)\"\n (DetailsToggled)=\"OnDetailsToggled()\"\n (MuteChanged)=\"OnComposerMuteChanged($event)\"\n (EndRequested)=\"OnEndCall()\">\n </mj-realtime-composer>\n }\n </div>\n\n <!-- RIGHT: TABBED SURFACE PANEL \u2014 disclosure-gated: it EXISTS once earned (level 2+,\n which the first delegation forces), peeked on demand via the strip's Details\n control, or always in review. Slides in from the right when it arrives \u2014 the\n panel's arrival IS the progress cue. -->\n @if (ShowPanelArea) {\n <!-- Resize handle (hidden while the panel is collapsed or a channel holds focus). -->\n @if (!PanelResizeDisabled) {\n <div class=\"call-resizer\"\n [class.call-resizer--active]=\"IsPanelResizing\"\n role=\"separator\" aria-orientation=\"vertical\"\n aria-label=\"Resize the surface panel (double-click to reset)\"\n title=\"Drag to resize \u2022 double-click to reset\"\n (mousedown)=\"OnPanelResizeStart($event)\"\n (dblclick)=\"OnPanelResizeReset()\"></div>\n }\n\n <div class=\"call-panel\" [style.width.px]=\"ChannelFocusMode ? null : PanelAreaSize\">\n <mj-realtime-surface-tabs\n [State]=\"State\"\n [DevMode]=\"DevMode\"\n [CurrentUser]=\"CurrentUser\"\n [EnvironmentID]=\"EnvironmentID\"\n [ShowActivityTab]=\"ShowActivityTab\"\n [ExtraArtifacts]=\"ReviewArtifacts\"\n [Fill]=\"ChannelFocusMode\"\n (OpenRunRequested)=\"OnOpenRunRequested($event)\"\n (CollapsedChange)=\"OnPanelCollapsedChange($event)\"\n (WideChanged)=\"OnPanelWideChanged($event)\">\n </mj-realtime-surface-tabs>\n </div>\n }\n\n <!-- FOCUS floating call pill: the main column is hidden, so the live call's\n identity + state + mute / show-thread / end controls ride over the surface. -->\n @if (!IsReviewing && ChannelFocusMode) {\n <div class=\"board-focus-pill\" role=\"status\" aria-label=\"Live call (board focus)\">\n <span class=\"board-focus-pill__orb\" [attr.data-state]=\"state\" aria-hidden=\"true\"></span>\n <span class=\"board-focus-pill__text\">\n <span class=\"board-focus-pill__name\">Voice for {{ AgentName }}</span>\n @if (FocusStateLabel(state); as label) {\n <span class=\"board-focus-pill__state\">{{ label }}</span>\n }\n </span>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n [class.board-focus-pill__btn--active]=\"FocusPillMuted\"\n (click)=\"OnFocusPillMute()\"\n [title]=\"FocusPillMuted ? 'Unmute' : 'Mute'\"\n [attr.aria-label]=\"FocusPillMuted ? 'Unmute microphone' : 'Mute microphone'\">\n <i class=\"fa-solid\" [class.fa-microphone]=\"!FocusPillMuted\" [class.fa-microphone-slash]=\"FocusPillMuted\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"board-focus-pill__btn\"\n (click)=\"OnFocusPillExit()\"\n title=\"Show thread\" aria-label=\"Show the conversation thread\">\n <i class=\"fa-solid fa-table-columns\" aria-hidden=\"true\"></i>\n </button>\n <button type=\"button\" class=\"board-focus-pill__btn board-focus-pill__btn--end\"\n (click)=\"OnFocusPillEnd()\"\n title=\"End call\" aria-label=\"End the call\">\n <i class=\"fa-solid fa-phone-slash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n </div>\n}\n\n<!-- SESSION REVIEW whiteboard pane body: the reviewed session's saved board, rehydrated\n locally (WhiteboardState.FromJSON) and rendered READ-ONLY (pan/zoom only). Registered\n as a template-based channel tab ONLY when a parseable Whiteboard state exists. -->\n<ng-template #reviewBoardTpl>\n @if (ReviewWhiteboard; as board) {\n <!-- The board host positions itself absolutely \u2014 give it a filling, positioned frame. -->\n <div class=\"review-board-host\">\n <mj-realtime-whiteboard\n [State]=\"board\"\n [AgentName]=\"AgentName\"\n [ReadOnly]=\"true\">\n </mj-realtime-whiteboard>\n </div>\n }\n</ng-template>\n\n<!-- SESSION REVIEW recording pane body: the reviewed session's recorded audio synced to its\n transcript, replayed via the shared storage media player (click-to-seek transcript + playhead\n highlight). Registered as a template-based channel tab ONLY when the reviewed session carries a\n RecordingFileID; the player resolves the authenticated audio itself from that file id. -->\n<ng-template #recordingTpl>\n <div class=\"review-recording-host\">\n <mj-storage-media-player\n [FileID]=\"ReviewRecordingFileID\"\n [Transcript]=\"ReviewCues\"\n [Provider]=\"Provider\">\n </mj-storage-media-player>\n </div>\n</ng-template>\n", styles: [":host {\n display: contents;\n}\n\n/* Fills the conversation panel IN PLACE: the chat area host provides the positioned\n context (position:relative on .chat-area) and this covers it edge to edge \u2014 header,\n thread and composer included. Opaque-ish page surface with a slight blur so the\n conversation underneath reads as \"paused\", not as an app-wide modal. */\n.call-overlay {\n position: absolute;\n inset: 0;\n z-index: 50;\n display: flex;\n background: color-mix(in srgb, var(--mj-bg-page) 94%, transparent);\n backdrop-filter: blur(8px);\n overflow: hidden;\n}\n\n/* Minimized: hidden (NOT destroyed) so the shell's session/view state survives while\n the host shows its floating \"on call\" pill. The audio session is untouched. */\n.call-overlay--hidden {\n display: none;\n}\n\n/* MAIN column: banner (top) \u2192 scrolling thread \u2192 channel strip \u2192 composer \u2192 controls.\n The 420px floor keeps the call column readable while the panel is dragged wide\n (the drag clamp's 70% cap is the other guard). */\n.call-main {\n flex: 1;\n min-width: 420px;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.call-body {\n flex: 1;\n overflow-y: auto;\n padding: 16px 22px;\n min-height: 0;\n scrollbar-width: thin;\n scrollbar-color: var(--mj-border-strong) transparent;\n}\n.call-body::-webkit-scrollbar {\n width: 6px;\n}\n.call-body::-webkit-scrollbar-thumb {\n background: var(--mj-border-strong);\n border-radius: 3px;\n}\n\n.call-connecting {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n min-height: 160px;\n}\n\n/* ---------- PURE-AUDIO HERO (disclosure level 0) ----------\n The orb IS the interface: it breathes with the audio inside expanding sound-wave\n rings, with a small voice equalizer underneath. Nothing to read, nothing to\n configure \u2014 text, panels and the composer are revealed later (or peeked on demand). */\n.call-body--hero {\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n}\n.hero {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n animation: hero-in 420ms cubic-bezier(0.2, 0.8, 0.3, 1);\n}\n@keyframes hero-in {\n from { opacity: 0; transform: scale(0.94); }\n to { opacity: 1; transform: scale(1); }\n}\n.hero__stage {\n position: relative;\n width: 220px;\n height: 220px;\n display: grid;\n place-items: center;\n margin-bottom: 4px;\n}\n/* The hero orb mirrors the app-bar orb's visual language at stage scale. */\n.hero__orb {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n position: relative;\n background: radial-gradient(circle at 35% 30%,\n var(--mj-brand-accent, var(--mj-brand-primary)),\n var(--mj-brand-primary) 60%,\n var(--mj-brand-primary-active, var(--mj-brand-primary)));\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n.hero__orb[data-state='speaking'] {\n animation: hero-orb-bob 0.9s ease-in-out infinite;\n}\n.hero__orb[data-state='listening'] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.hero__orb[data-state='thinking'] {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-warning) 14%, transparent),\n 0 0 40px color-mix(in srgb, var(--mj-status-warning) 35%, transparent);\n}\n@keyframes hero-orb-bob {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-4px); }\n}\n/* Expanding sound-wave rings (staggered). */\n.hero__ring {\n position: absolute;\n width: 120px;\n height: 120px;\n border-radius: 50%;\n border: 2px solid color-mix(in srgb, var(--mj-brand-primary) 55%, transparent);\n opacity: 0;\n animation: hero-wave 2.7s cubic-bezier(0.2, 0.6, 0.4, 1) infinite;\n}\n.hero__ring--2 { animation-delay: 0.9s; }\n.hero__ring--3 { animation-delay: 1.8s; }\n@keyframes hero-wave {\n 0% { transform: scale(1); opacity: 0.5; }\n 70% { opacity: 0.1; }\n 100% { transform: scale(1.85); opacity: 0; }\n}\n/* Voice equalizer: lively while the agent speaks, near-still otherwise. */\n.hero__eq {\n display: flex;\n align-items: center;\n gap: 4px;\n height: 30px;\n margin-bottom: 6px;\n}\n.hero__eq span {\n width: 4px;\n height: 8px;\n border-radius: var(--mj-radius-full, 9999px);\n background: var(--mj-brand-accent, var(--mj-brand-primary));\n opacity: 0.55;\n}\n.hero[data-state='speaking'] .hero__eq span {\n animation: hero-eq 1.05s ease-in-out infinite;\n}\n.hero__eq span:nth-child(1) { animation-delay: 0s; }\n.hero__eq span:nth-child(2) { animation-delay: 0.14s; }\n.hero__eq span:nth-child(3) { animation-delay: 0.28s; }\n.hero__eq span:nth-child(4) { animation-delay: 0.07s; }\n.hero__eq span:nth-child(5) { animation-delay: 0.21s; }\n.hero__eq span:nth-child(6) { animation-delay: 0.35s; }\n.hero__eq span:nth-child(7) { animation-delay: 0.1s; }\n.hero__eq span:nth-child(8) { animation-delay: 0.24s; }\n.hero__eq span:nth-child(9) { animation-delay: 0.03s; }\n@keyframes hero-eq {\n 0%, 100% { height: 7px; opacity: 0.6; }\n 50% { height: 28px; opacity: 1; }\n}\n.hero__name {\n font-size: 19px;\n font-weight: 800;\n letter-spacing: -0.01em;\n color: var(--mj-text-primary);\n}\n.hero__sub {\n font-size: 12.5px;\n color: var(--mj-text-muted);\n}\n/* ---------- AUDIO-REACTIVE MODE (data-audio-live, set by the rAF meter loop) ----------\n With real audio metering the orb stops acting and starts REACTING: scale + glow follow\n the smoothed output envelope (--voice-out) like a speaker cone, the EQ renders the true\n spectrum (--eq-1..9), and [data-voice-dir] colors the visuals by who is speaking\n (agent = brand, user = green). Without metering (driver attached no meters) the\n attribute stays 'false' and the turn-state keyframe animations above stay in charge. */\n.call-overlay[data-audio-live='true'] .hero__orb {\n animation: none;\n transform: scale(calc(1 + var(--voice-out, 0) * 0.18));\n transition: transform 70ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__ring {\n opacity: calc(var(--voice-out, 0) * 0.55);\n animation-duration: 2.1s;\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__orb {\n box-shadow:\n 0 0 0 8px color-mix(in srgb, var(--mj-status-success) 16%, transparent),\n 0 0 48px color-mix(in srgb, var(--mj-status-success) 45%, transparent);\n background: radial-gradient(circle at 35% 30%,\n color-mix(in srgb, var(--mj-status-success) 60%, white), var(--mj-brand-primary) 65%);\n}\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__ring {\n border-color: color-mix(in srgb, var(--mj-status-success) 55%, transparent);\n}\n/* True-spectrum EQ: per-bar heights from the meter's bins. */\n.call-overlay[data-audio-live='true'] .hero__eq span {\n animation: none;\n transition: height 70ms linear, opacity 120ms linear;\n}\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(1) { height: calc(6px + var(--eq-1, 0) * 24px); opacity: calc(0.45 + var(--eq-1, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(2) { height: calc(6px + var(--eq-2, 0) * 24px); opacity: calc(0.45 + var(--eq-2, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(3) { height: calc(6px + var(--eq-3, 0) * 24px); opacity: calc(0.45 + var(--eq-3, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(4) { height: calc(6px + var(--eq-4, 0) * 24px); opacity: calc(0.45 + var(--eq-4, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(5) { height: calc(6px + var(--eq-5, 0) * 24px); opacity: calc(0.45 + var(--eq-5, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(6) { height: calc(6px + var(--eq-6, 0) * 24px); opacity: calc(0.45 + var(--eq-6, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(7) { height: calc(6px + var(--eq-7, 0) * 24px); opacity: calc(0.45 + var(--eq-7, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(8) { height: calc(6px + var(--eq-8, 0) * 24px); opacity: calc(0.45 + var(--eq-8, 0) * 0.55); }\n.call-overlay[data-audio-live='true'] .hero__eq span:nth-child(9) { height: calc(6px + var(--eq-9, 0) * 24px); opacity: calc(0.45 + var(--eq-9, 0) * 0.55); }\n.call-overlay[data-audio-live='true'][data-voice-dir='user'] .hero__eq span {\n background: var(--mj-status-success);\n}\n\n/* The quiet first reveal: show the conversation as text (raises disclosure to level 1). */\n.hero__reveal {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-top: 18px;\n padding: 6px 14px;\n border-radius: var(--mj-radius-full, 9999px);\n border: 1px dashed var(--mj-border-strong);\n background: transparent;\n color: var(--mj-text-muted);\n font-family: inherit;\n font-size: 11.5px;\n font-weight: 600;\n cursor: pointer;\n transition: color 140ms ease, border-color 140ms ease, background 140ms ease;\n}\n.hero__reveal:hover {\n color: var(--mj-text-secondary);\n border-color: var(--mj-brand-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n}\n.hero__reveal i {\n font-size: 11px;\n}\n\n/* Ephemeral narration shown under the banner when no working card anchors it */\n.banner-live-note {\n display: flex;\n align-items: flex-start;\n gap: 7px;\n padding: 8px 22px 0;\n font-size: 12px;\n font-style: italic;\n color: var(--mj-text-muted);\n animation: overlay-note-in 220ms ease;\n}\n.banner-live-note i {\n font-size: 10px;\n margin-top: 3px;\n flex-shrink: 0;\n}\n@keyframes overlay-note-in {\n from { opacity: 0; transform: translateY(2px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* ---------- FOCUS-BOARD mode (whiteboard \"Focus board\" toggle) ----------\n The main call column collapses away entirely and the surface panel (whiteboard tab)\n fills the overlay; a compact floating call pill keeps the live call reachable. */\n.call-overlay.board-focus .call-main {\n display: none;\n}\n.call-overlay.board-focus mj-realtime-surface-tabs {\n flex: 1;\n min-width: 0;\n}\n\n/* Floating call pill: orb + state + mute / show-thread / end (per the whiteboard mockup). */\n.board-focus-pill {\n position: absolute;\n left: 64px;\n top: 12px;\n z-index: 60;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 7px 9px 7px 12px;\n border-radius: var(--mj-radius-full, 999px);\n background: color-mix(in srgb, var(--mj-bg-surface-card) 92%, transparent);\n backdrop-filter: blur(8px);\n border: 1px solid var(--mj-border-strong);\n box-shadow: var(--mj-shadow-xl, 0 12px 32px rgba(0, 0, 0, 0.18));\n animation: overlay-note-in 220ms ease;\n}\n.board-focus-pill__orb {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent);\n}\n.board-focus-pill__orb[data-state='speaking'],\n.board-focus-pill__orb[data-state='listening'] {\n animation: board-pill-pulse 1.6s ease-in-out infinite;\n}\n.board-focus-pill__orb[data-state='error'] {\n background: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 18%, transparent);\n}\n@keyframes board-pill-pulse {\n 0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 18%, transparent); }\n 50% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--mj-brand-primary) 8%, transparent); }\n}\n.board-focus-pill__text {\n display: flex;\n flex-direction: column;\n min-width: 0;\n}\n.board-focus-pill__name {\n font-size: 12px;\n font-weight: 700;\n line-height: 1.2;\n white-space: nowrap;\n color: var(--mj-text-primary);\n}\n.board-focus-pill__state {\n font-size: 10.5px;\n font-weight: 600;\n white-space: nowrap;\n color: var(--mj-brand-primary);\n}\n.board-focus-pill__btn {\n width: 30px;\n height: 30px;\n border-radius: 50%;\n border: 1px solid var(--mj-border-strong);\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n font-size: 11px;\n display: grid;\n place-items: center;\n font-family: inherit;\n flex-shrink: 0;\n}\n.board-focus-pill__btn:hover {\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--active {\n background: var(--mj-bg-surface-active);\n color: var(--mj-text-primary);\n}\n.board-focus-pill__btn--end {\n background: var(--mj-status-error);\n border-color: var(--mj-status-error);\n color: var(--mj-text-inverse, #fff);\n}\n\n/* ---------- Session review mode ---------- */\n/* Subtle archival tint so a glance says \"this is history, not a live call\". */\n.call-overlay--review {\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--mj-text-muted) 4%, transparent), transparent 120px),\n var(--mj-bg-surface);\n}\n\n/* The review whiteboard pane body: the board component positions itself absolutely\n (inset: 0), so its template-based pane needs a filling, positioned frame. */\n.review-board-host {\n position: relative;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* The review recording pane body: the evidence player fills its host (:host height:100%),\n so its template-based pane needs a filling, constrained frame. */\n.review-recording-host {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n.review-recording-host mj-realtime-evidence-playback {\n flex: 1;\n min-height: 0;\n}\n\n/* ---------- flex layout (main column | resize handle | surface panel) ---------- */\n/* Token-styled resize handle: invisible until hover, brand-tinted while interacting.\n 7px hit target, col-resize cursor; removed (not rendered) while collapsed / focus. */\n.call-resizer {\n flex: 0 0 7px;\n width: 7px;\n cursor: col-resize;\n background: transparent;\n transition: background 140ms ease;\n}\n.call-resizer:hover,\n.call-resizer--active {\n background: color-mix(in srgb, var(--mj-brand-primary) 26%, transparent);\n}\n/* The surface panel: a plain fixed-width flex item (inline width set by the overlay;\n collapsed = the slim strip width). flex-shrink lets it yield to the call column's\n min-width on tight overlays; overflow hidden keeps the flex height chain intact so\n the panel's content scrolls internally. */\n.call-panel {\n flex: 0 1 auto;\n min-width: 0;\n display: flex;\n overflow: hidden;\n /* Disclosure-gated: the panel is CREATED when earned/peeked \u2014 its arrival slides in\n from the right so the reveal doubles as the progress cue. */\n animation: panel-in 420ms cubic-bezier(0.25, 0.8, 0.3, 1);\n}\n@keyframes panel-in {\n from { opacity: 0; transform: translateX(48px); }\n to { opacity: 1; transform: translateX(0); }\n}\n.call-panel > * {\n flex: 1;\n min-width: 0;\n min-height: 0;\n}\n/* Board-focus fill: the panel owns the whole overlay (the main column is hidden). */\n.call-overlay.board-focus .call-panel {\n flex: 1;\n}\n"] }]
1242
1879
  }], () => [], { Hidden: [{
1243
1880
  type: Input
1244
1881
  }], AgentName: [{
@@ -1247,6 +1884,36 @@ export class RealtimeSessionOverlayComponent {
1247
1884
  type: Input
1248
1885
  }], EnvironmentID: [{
1249
1886
  type: Input
1887
+ }], Chrome: [{
1888
+ type: Input
1889
+ }], ConsoleBreakpointPx: [{
1890
+ type: Input
1891
+ }], Compact: [{
1892
+ type: Input
1893
+ }], AutoHideControls: [{
1894
+ type: Input
1895
+ }], AllowTextReveal: [{
1896
+ type: Input
1897
+ }], ShowCaptionsControl: [{
1898
+ type: Input
1899
+ }], ShowDensityPicker: [{
1900
+ type: Input
1901
+ }], ShowMinimize: [{
1902
+ type: Input
1903
+ }], ShowEnd: [{
1904
+ type: Input
1905
+ }], ShowSurfacePanel: [{
1906
+ type: Input
1907
+ }], ShowChannels: [{
1908
+ type: Input
1909
+ }], ShowActivityRail: [{
1910
+ type: Input
1911
+ }], ShowDevLinks: [{
1912
+ type: Input
1913
+ }], AllowResize: [{
1914
+ type: Input
1915
+ }], UiConfig: [{
1916
+ type: Input
1250
1917
  }], ReviewData: [{
1251
1918
  type: Input
1252
1919
  }], Ended: [{
@@ -1257,6 +1924,22 @@ export class RealtimeSessionOverlayComponent {
1257
1924
  type: Output
1258
1925
  }], ReviewClosed: [{
1259
1926
  type: Output
1927
+ }], Minimized: [{
1928
+ type: Output
1929
+ }], TextRevealed: [{
1930
+ type: Output
1931
+ }], ChromeChanged: [{
1932
+ type: Output
1933
+ }], ConnectionStateChanged: [{
1934
+ type: Output
1935
+ }], MuteChanged: [{
1936
+ type: Output
1937
+ }], DensityChanged: [{
1938
+ type: Output
1939
+ }], SurfacePanelResized: [{
1940
+ type: Output
1941
+ }], ControlInvoked: [{
1942
+ type: Output
1260
1943
  }], surfaceTabsRef: [{
1261
1944
  type: ViewChild,
1262
1945
  args: [RealtimeSurfaceTabsComponent]
@@ -1266,9 +1949,12 @@ export class RealtimeSessionOverlayComponent {
1266
1949
  }], reviewBoardTpl: [{
1267
1950
  type: ViewChild,
1268
1951
  args: ['reviewBoardTpl']
1952
+ }], recordingTpl: [{
1953
+ type: ViewChild,
1954
+ args: ['recordingTpl']
1269
1955
  }], OnDocumentKeydown: [{
1270
1956
  type: HostListener,
1271
1957
  args: ['document:keydown', ['$event']]
1272
1958
  }] }); })();
1273
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeSessionOverlayComponent, { className: "RealtimeSessionOverlayComponent", filePath: "src/lib/components/realtime/realtime-session-overlay.component.ts", lineNumber: 118 }); })();
1959
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeSessionOverlayComponent, { className: "RealtimeSessionOverlayComponent", filePath: "src/lib/components/realtime/realtime-session-overlay.component.ts", lineNumber: 128 }); })();
1274
1960
  //# sourceMappingURL=realtime-session-overlay.component.js.map