@opengeni/react 4.6.1 → 4.6.2-canary.39853438886001

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 (467) hide show
  1. package/dist/app/app-state.js +13 -13
  2. package/dist/app/components/Composer.js +14 -14
  3. package/dist/app/components/artifacts/artifact-library.js +5 -5
  4. package/dist/app/components/artifacts/chat-interactive-block.js +10 -10
  5. package/dist/app/components/artifacts/inline-chat-image.js +14 -14
  6. package/dist/app/components/artifacts/retained-file-preview.js +16 -16
  7. package/dist/app/components/artifacts/retained-text-preview.js +2 -2
  8. package/dist/app/components/artifacts/site-conversations.js +5 -4
  9. package/dist/app/components/artifacts/site-data.js +2 -2
  10. package/dist/app/components/artifacts/site-publishing.js +11 -11
  11. package/dist/app/components/artifacts/site-settings.js +15 -15
  12. package/dist/app/components/billing-class-mark.js +3 -3
  13. package/dist/app/components/capabilities/capability-detail-sheet.js +2 -2
  14. package/dist/app/components/capabilities/capability-logo-source.js +7 -7
  15. package/dist/app/components/capabilities/native-connect-readiness.js +1 -0
  16. package/dist/app/components/capabilities/native-connect-readiness.js.map +1 -1
  17. package/dist/app/components/capabilities/native-connect-setup.js +6 -6
  18. package/dist/app/components/capabilities/perform-capability-action.js +2 -1
  19. package/dist/app/components/capabilities/prepared-mcp-setup-card.js +8 -8
  20. package/dist/app/components/capabilities/session-capability-card.js +32 -31
  21. package/dist/app/components/capabilities/session-capability-card.js.map +1 -1
  22. package/dist/app/components/capabilities/session-chrome-card.js +14 -14
  23. package/dist/app/components/capabilities/session-custom-mcp-card.js +9 -9
  24. package/dist/app/components/capabilities/session-github-card.js +9 -9
  25. package/dist/app/components/capabilities/session-machine-card.js +14 -14
  26. package/dist/app/components/command-menu/chat-status.js +6 -5
  27. package/dist/app/components/command-menu/chat-switcher.d.ts +2 -0
  28. package/dist/app/components/command-menu/chat-switcher.js +26 -9
  29. package/dist/app/components/command-menu/chat-switcher.js.map +1 -1
  30. package/dist/app/components/command-menu/command-menu.js +16 -15
  31. package/dist/app/components/command-menu/command-menu.js.map +1 -1
  32. package/dist/app/components/command-menu/shortcut-sheet.js +8 -8
  33. package/dist/app/components/composer-mobile-plus-panel.js +5 -5
  34. package/dist/app/components/composer-mobile-plus.js +1 -1
  35. package/dist/app/components/credit-required-gate.js +1 -1
  36. package/dist/app/components/credit-required-prompt.js +4 -4
  37. package/dist/app/components/desktop/app-shots.js +5 -5
  38. package/dist/app/components/desktop/this-computer.js +4 -4
  39. package/dist/app/components/feedback.js +3 -3
  40. package/dist/app/components/github-app-connect-launcher.js +1 -1
  41. package/dist/app/components/machines/connect-machine-dialog.js +3 -3
  42. package/dist/app/components/markdown.js +4 -4
  43. package/dist/app/components/models/models-ui.js +2 -2
  44. package/dist/app/components/pickers.js +5 -5
  45. package/dist/app/components/rail/rail-context.js +3 -3
  46. package/dist/app/components/rail/rail-sections.js +28 -27
  47. package/dist/app/components/rail/session-header.d.ts +6 -5
  48. package/dist/app/components/rail/session-header.js +20 -19
  49. package/dist/app/components/rail/session-list.js +27 -26
  50. package/dist/app/components/rail/session-row-hover-details.js +7 -6
  51. package/dist/app/components/rail/session-shell.js +51 -53
  52. package/dist/app/components/rail/site-session-group-heading.js +2 -1
  53. package/dist/app/components/session/agent-configuration-panel.js +6 -6
  54. package/dist/app/components/session/banners.js +6 -6
  55. package/dist/app/components/session/conversation-find.js +3 -3
  56. package/dist/app/components/session/editable-artifacts-workspace.js +17 -17
  57. package/dist/app/components/session/failed-session-banner.js +5 -5
  58. package/dist/app/components/session/human-input.js +26 -26
  59. package/dist/app/components/session/inspector.js +37 -37
  60. package/dist/app/components/session/message-actions.js +3 -3
  61. package/dist/app/components/session/message-resource-attachments.js +5 -5
  62. package/dist/app/components/session/model-recovery-notice.js +3 -3
  63. package/dist/app/components/session/new-session-settings-menu.js +4 -4
  64. package/dist/app/components/session/sandbox-switcher.js +8 -8
  65. package/dist/app/components/session/sandbox-workspace.js +36 -36
  66. package/dist/app/components/session/search-markdown-highlight.js +5 -5
  67. package/dist/app/components/session/search-results-view.js +6 -6
  68. package/dist/app/components/session/session-admission-notice.js +4 -4
  69. package/dist/app/components/session/session-machine-access-notice.js +4 -4
  70. package/dist/app/components/session/session-search-dialog.js +6 -6
  71. package/dist/app/components/session/session-tenancy-control.js +4 -4
  72. package/dist/app/components/session/session-usage-limit-section.js +3 -3
  73. package/dist/app/components/session/session-wait-status.js +2 -1
  74. package/dist/app/components/session/session-wait-status.js.map +1 -1
  75. package/dist/app/components/session/subagents.js +6 -5
  76. package/dist/app/components/session/subscription-account-indicator.d.ts +35 -0
  77. package/dist/app/components/session/{codex-account-indicator.js → subscription-account-indicator.js} +23 -10
  78. package/dist/app/components/session/subscription-account-menu.d.ts +11 -12
  79. package/dist/app/components/session/subscription-account-menu.js +1 -1
  80. package/dist/app/components/session/subscription-account.d.ts +105 -0
  81. package/dist/app/components/usage/usage-entry.js +3 -3
  82. package/dist/app/components/workspace-composer-plus.js +10 -10
  83. package/dist/app/embed/session-app.js +154 -156
  84. package/dist/app/lib/actions/actions-provider.js +8 -8
  85. package/dist/app/lib/actions/chat-switcher.d.ts +19 -7
  86. package/dist/app/lib/codex-usage-query.js +2 -2
  87. package/dist/app/lib/composer-connectors.js +8 -8
  88. package/dist/app/lib/console-text.js +2 -2
  89. package/dist/app/lib/drive-connect-return.d.ts +26 -0
  90. package/dist/app/lib/drive-connect-return.js +16 -0
  91. package/dist/app/lib/events.js +7 -7
  92. package/dist/app/lib/failed-session-copy.js +3 -3
  93. package/dist/app/lib/model-access-onboarding.js +3 -3
  94. package/dist/app/lib/move-chat-to-machine.js +3 -3
  95. package/dist/app/lib/session-branch-cache.js +6 -5
  96. package/dist/app/lib/session-group-pagination.js +6 -5
  97. package/dist/app/lib/session-hover-facts.js +6 -5
  98. package/dist/app/lib/session-rail.js +2 -1
  99. package/dist/app/lib/sessions-group.js +5 -4
  100. package/dist/app/lib/turn-failure-analytics.js +5 -5
  101. package/dist/app/lib/use-workspace-machines.js +4 -4
  102. package/dist/app/lib/use-workspace-model-catalog.js +3 -3
  103. package/dist/app/lib/use-workspace-rigs.js +2 -2
  104. package/dist/app/lib/workspace-navigation-preference.js +4 -4
  105. package/dist/app/lib/workspace-scope-context.js +3 -3
  106. package/dist/app/routes/artifacts.js +50 -49
  107. package/dist/app/routes/artifacts.js.map +1 -1
  108. package/dist/app/routes/retained-artifact.js +23 -23
  109. package/dist/app/routes/session.js +92 -91
  110. package/dist/app/routes/sessions-index.js +52 -52
  111. package/dist/app/routes/site-view.js +35 -34
  112. package/dist/app/routes/site-view.js.map +1 -1
  113. package/dist/artifacts-document.js +4 -4
  114. package/dist/artifacts-presentation.js +4 -4
  115. package/dist/artifacts-spreadsheet.js +4 -4
  116. package/dist/artifacts.js +10 -10
  117. package/dist/{browser-viewer-YQC6BFMS.js → browser-viewer-VKIAULG7.js} +4 -4
  118. package/dist/{chat-interactive-block-Z22YHDRU.js → chat-interactive-block-JGYSW4KB.js} +5 -5
  119. package/dist/{chunk-WN2MIB4Q.js → chunk-22V2QCEM.js} +2 -2
  120. package/dist/{chunk-SJPAKWWP.js → chunk-2BISI664.js} +2 -2
  121. package/dist/{chunk-O5UOCMH4.js → chunk-2LPJVEWY.js} +3 -3
  122. package/dist/{chunk-NPDL4W4Y.js → chunk-2MLJHNKJ.js} +108 -109
  123. package/dist/chunk-2MLJHNKJ.js.map +1 -0
  124. package/dist/{chunk-JI6WPLZB.js → chunk-2SBCOGQN.js} +4 -4
  125. package/dist/{chunk-6JF7C3U3.js → chunk-2WERWWSS.js} +2 -2
  126. package/dist/{chunk-4XKT4ENZ.js → chunk-2Y3QL3DA.js} +4 -4
  127. package/dist/{chunk-KVMFT6FN.js → chunk-3FL3F4WF.js} +2 -2
  128. package/dist/{chunk-4VJOZJDV.js → chunk-3JPQAIYJ.js} +2 -2
  129. package/dist/{chunk-27EORKJ4.js → chunk-3N4LNBJF.js} +2 -2
  130. package/dist/{chunk-7JZSFJHB.js → chunk-3OJU3RUR.js} +2 -2
  131. package/dist/{chunk-NV5OYV3G.js → chunk-3U6K36QA.js} +5 -5
  132. package/dist/{chunk-MDRLP5DO.js → chunk-3W7ROHN4.js} +3 -3
  133. package/dist/{chunk-3MQM4ZOC.js → chunk-3Y6ZR2VN.js} +3 -3
  134. package/dist/{chunk-HXZIX7MR.js → chunk-47AB65IO.js} +4 -4
  135. package/dist/{chunk-DNYD7AUG.js → chunk-4BOLIZ7Y.js} +3 -3
  136. package/dist/{chunk-4XO346BW.js → chunk-4FBJFQFU.js} +2 -2
  137. package/dist/{chunk-BWKZEW6K.js → chunk-4T3E56NT.js} +3 -3
  138. package/dist/{chunk-4PC7LIQK.js → chunk-4ZMUWRFF.js} +3 -3
  139. package/dist/{chunk-TM2ZLY3Q.js → chunk-5OLFYSVG.js} +13 -13
  140. package/dist/{chunk-D4QOZXPM.js → chunk-5WBRM7V4.js} +5 -5
  141. package/dist/{chunk-GXK4CUAB.js → chunk-5WQKTT5Z.js} +2 -2
  142. package/dist/{chunk-6N7W3TOV.js → chunk-63BPMTBM.js} +2 -2
  143. package/dist/{chunk-R5YZ3ROM.js → chunk-63PQKTTT.js} +2 -2
  144. package/dist/{chunk-WLZVXCPZ.js → chunk-6GO6WN22.js} +3 -3
  145. package/dist/{chunk-6PQBKMUL.js → chunk-6IIA63VD.js} +11 -7
  146. package/dist/chunk-6IIA63VD.js.map +1 -0
  147. package/dist/{chunk-XDCZBOEY.js → chunk-6LGBGPBU.js} +1 -1
  148. package/dist/{chunk-FMATVQFW.js → chunk-6XXMG5YP.js} +9 -9
  149. package/dist/{chunk-M5E3CKSE.js → chunk-7QWWE5XL.js} +2 -2
  150. package/dist/{chunk-RABKQFTA.js → chunk-A2HCAMRH.js} +2 -2
  151. package/dist/{chunk-6K3WNTRI.js → chunk-AODISQO6.js} +2 -2
  152. package/dist/{chunk-C6JL6PBL.js → chunk-AQDRH6VF.js} +3 -3
  153. package/dist/{chunk-72XEFLMD.js → chunk-ASWINLSW.js} +2 -2
  154. package/dist/{chunk-ZPZYYSHW.js → chunk-BVMDON5T.js} +8 -8
  155. package/dist/{chunk-EURDMOXZ.js → chunk-BYJZUCOQ.js} +2 -2
  156. package/dist/{chunk-BQWUXC55.js → chunk-CAVAVT5S.js} +9 -9
  157. package/dist/{chunk-ZWACUOU5.js → chunk-CDBGAAJY.js} +3 -3
  158. package/dist/chunk-CDBGAAJY.js.map +1 -0
  159. package/dist/{chunk-BPD4IJBG.js → chunk-CG6CJMIS.js} +2 -2
  160. package/dist/{chunk-B254JG72.js → chunk-CQX24PT7.js} +2 -2
  161. package/dist/{chunk-65K5LV4L.js → chunk-CTCOVPZU.js} +51 -50
  162. package/dist/{chunk-65K5LV4L.js.map → chunk-CTCOVPZU.js.map} +1 -1
  163. package/dist/{chunk-HJTTDLF6.js → chunk-CY3NO4JN.js} +4 -4
  164. package/dist/{chunk-OLUG4IFF.js → chunk-D2RLPRSG.js} +2 -2
  165. package/dist/{chunk-74NIHMAT.js → chunk-D653JH6D.js} +3 -3
  166. package/dist/{chunk-2LUO22FR.js → chunk-DAI5CTQF.js} +4 -4
  167. package/dist/{chunk-HTKVYEVB.js → chunk-DBM4ZXDN.js} +2 -2
  168. package/dist/{chunk-MB2DTUEG.js → chunk-DNYKLU3K.js} +4 -4
  169. package/dist/{chunk-26P7B46A.js → chunk-DXYARXK3.js} +3 -3
  170. package/dist/{chunk-JQKURO3S.js → chunk-E4MQRQ6Z.js} +2 -2
  171. package/dist/{chunk-KW53DBY7.js → chunk-EBW2E32G.js} +2 -2
  172. package/dist/{chunk-O73DRA77.js → chunk-ENKBO7FC.js} +4 -4
  173. package/dist/{chunk-FVCLVASD.js → chunk-FI3KRVOF.js} +24 -24
  174. package/dist/{chunk-WWM7WVD2.js → chunk-GABFKHQE.js} +2 -2
  175. package/dist/chunk-GLCOKWLH.js +42 -0
  176. package/dist/chunk-GLCOKWLH.js.map +1 -0
  177. package/dist/{chunk-32HA7DXJ.js → chunk-GNBRB3DH.js} +25 -25
  178. package/dist/{chunk-CEQUNJJY.js → chunk-HPALK6LD.js} +2 -2
  179. package/dist/{chunk-LR7U3HCD.js → chunk-HPSATFLQ.js} +4 -4
  180. package/dist/{chunk-O4AWXTZQ.js → chunk-HUYLQW75.js} +6 -6
  181. package/dist/{chunk-PQCFW2KT.js → chunk-IDDHH5AQ.js} +29 -24
  182. package/dist/chunk-IDDHH5AQ.js.map +1 -0
  183. package/dist/{chunk-ZZUKJ5HQ.js → chunk-IG2ERMSB.js} +2 -2
  184. package/dist/{chunk-2SZ3KC5S.js → chunk-IMKIIS4Y.js} +2 -2
  185. package/dist/{chunk-CA64OZLZ.js → chunk-IRXPXDY5.js} +4 -4
  186. package/dist/{chunk-I57HZMMA.js → chunk-IXIE3H43.js} +2 -2
  187. package/dist/{chunk-OWJD3V4V.js → chunk-J3YQ3554.js} +2 -2
  188. package/dist/{chunk-BCFHMBMK.js → chunk-J5HZBHU4.js} +7 -7
  189. package/dist/{chunk-UHMMRQGU.js → chunk-KD7GNHOP.js} +3 -3
  190. package/dist/{chunk-X7Z4B5GK.js → chunk-KPL7EXS6.js} +2 -2
  191. package/dist/{chunk-GTR2WUBA.js → chunk-KX6M5CJI.js} +1 -1
  192. package/dist/{chunk-CYO5HL4C.js → chunk-KYL2BPS2.js} +2 -2
  193. package/dist/{chunk-QLDMF7MH.js → chunk-LC5HE7PZ.js} +2 -2
  194. package/dist/{chunk-3SMJ2BIQ.js → chunk-LGD5RESZ.js} +7 -33
  195. package/dist/chunk-LGD5RESZ.js.map +1 -0
  196. package/dist/{chunk-4ICPJZAB.js → chunk-LMYSYG6U.js} +2 -2
  197. package/dist/{chunk-3ZZOE4SN.js → chunk-LOCDYMD6.js} +4 -4
  198. package/dist/{chunk-DNWBLT4U.js → chunk-MASBMJ2M.js} +2 -2
  199. package/dist/{chunk-NZMQDUOD.js → chunk-MU6HYDNF.js} +5 -5
  200. package/dist/{chunk-GMVKCEBG.js → chunk-MY6QEDZ2.js} +2 -2
  201. package/dist/{chunk-SK57RWSA.js → chunk-MZGKQ5WV.js} +2 -2
  202. package/dist/{chunk-HSQRYJ4P.js → chunk-N52SFH2K.js} +2 -2
  203. package/dist/{chunk-MY6VZVCP.js → chunk-OAJX5CEL.js} +4 -4
  204. package/dist/{chunk-IWIMIRV3.js → chunk-OSJTLQXA.js} +9 -4
  205. package/dist/chunk-OSJTLQXA.js.map +1 -0
  206. package/dist/{chunk-FH75NNLB.js → chunk-OZ5NAXCP.js} +2 -2
  207. package/dist/{chunk-PEGNFZ3F.js → chunk-P5S777YI.js} +2 -2
  208. package/dist/{chunk-LY6727IF.js → chunk-PPSVFNEA.js} +1 -1
  209. package/dist/{chunk-K4FDZVMQ.js → chunk-PR42VIVY.js} +2 -2
  210. package/dist/{chunk-I74J2UZ2.js → chunk-PWMZCW4H.js} +8 -8
  211. package/dist/{chunk-6YDXUE5H.js → chunk-PYA2O3WS.js} +2 -2
  212. package/dist/{chunk-GUIZDI5T.js → chunk-Q2PA4KZS.js} +2 -2
  213. package/dist/{chunk-JJ7VMIEQ.js → chunk-QCANSTLU.js} +64 -64
  214. package/dist/{chunk-DZH2HTTB.js → chunk-R27GW5WV.js} +1 -1
  215. package/dist/chunk-R4U4EWZG.js +414 -0
  216. package/dist/chunk-R4U4EWZG.js.map +1 -0
  217. package/dist/{chunk-DAQG3C4W.js → chunk-R6JY22SG.js} +29 -29
  218. package/dist/{chunk-7OIBLMDP.js → chunk-RAQISW42.js} +2 -2
  219. package/dist/{chunk-DNLXQUBX.js → chunk-RLS6PTL4.js} +32 -44
  220. package/dist/chunk-RLS6PTL4.js.map +1 -0
  221. package/dist/{chunk-CPKD2YGK.js → chunk-RNU5CUPI.js} +2 -2
  222. package/dist/{chunk-467VRP4E.js → chunk-RO5YZQHZ.js} +3 -3
  223. package/dist/{chunk-SSZROOOI.js → chunk-ROLXYGT5.js} +2 -2
  224. package/dist/{chunk-6VITVQ23.js → chunk-SHJ5SQOW.js} +7 -7
  225. package/dist/{chunk-F4B2YYCN.js → chunk-TAWFXRXB.js} +3 -3
  226. package/dist/{chunk-G3FU2IF7.js → chunk-TB7UW5BB.js} +3 -3
  227. package/dist/{chunk-NF6Q4XTN.js → chunk-TQF2I5SY.js} +3 -3
  228. package/dist/{chunk-MM7OKOTE.js → chunk-UBXHP224.js} +43 -18
  229. package/dist/chunk-UBXHP224.js.map +1 -0
  230. package/dist/{chunk-M7A226EQ.js → chunk-UL44R3W2.js} +2 -2
  231. package/dist/{chunk-GTR5NLVP.js → chunk-VCRQPLKS.js} +2 -2
  232. package/dist/{chunk-MHIYYHCL.js → chunk-VJU3ET7N.js} +12 -12
  233. package/dist/{chunk-RXMIXKB3.js → chunk-WGJUARF3.js} +13 -13
  234. package/dist/{chunk-NWSAF6P5.js → chunk-WJDH7CH5.js} +22 -1
  235. package/dist/chunk-WJDH7CH5.js.map +1 -0
  236. package/dist/{chunk-LG67ZNRU.js → chunk-WK2ZCTUF.js} +2 -2
  237. package/dist/{chunk-XGE75VPK.js → chunk-WNARM3LY.js} +3 -3
  238. package/dist/{chunk-RNBH5TSM.js → chunk-WQ2RTQYO.js} +2 -2
  239. package/dist/chunk-X5XEYQ4B.js +84 -0
  240. package/dist/chunk-X5XEYQ4B.js.map +1 -0
  241. package/dist/{chunk-W57QFLQD.js → chunk-X6JIGNLI.js} +4 -4
  242. package/dist/{chunk-CKA4SJRK.js → chunk-XIQDCQX7.js} +2 -2
  243. package/dist/{chunk-AJ7PY4FJ.js → chunk-Y3GCG6CA.js} +2 -2
  244. package/dist/{chunk-2K27S6LK.js → chunk-Y565N5ZJ.js} +24 -7
  245. package/dist/chunk-Y565N5ZJ.js.map +1 -0
  246. package/dist/{chunk-ZRB4M3GG.js → chunk-YC5KH7ZB.js} +3 -3
  247. package/dist/{chunk-4RY64KQY.js → chunk-YJUKLLNP.js} +2 -2
  248. package/dist/{chunk-OHG2UQ2O.js → chunk-YWP2H7SX.js} +60 -22
  249. package/dist/chunk-YWP2H7SX.js.map +1 -0
  250. package/dist/{chunk-24POWVUY.js → chunk-ZDJNRRVS.js} +4 -4
  251. package/dist/{chunk-S6WJ6TUT.js → chunk-ZEVJGMNB.js} +2 -2
  252. package/dist/chunk-ZEVJGMNB.js.map +1 -0
  253. package/dist/{chunk-M7YEZR6X.js → chunk-ZITMDA4C.js} +2 -2
  254. package/dist/components/model-policy-picker.d.ts +2 -0
  255. package/dist/composer.js +6 -6
  256. package/dist/{computer-viewer-BWVNDFTO.js → computer-viewer-6CRLWQHP.js} +5 -5
  257. package/dist/connect.js +6 -6
  258. package/dist/connection-service-presentation.d.ts +1 -4
  259. package/dist/desktop.js +2 -2
  260. package/dist/editor.js +2 -2
  261. package/dist/{embedded-new-voice-GBXSZ3TO.js → embedded-new-voice-KILV3RYM.js} +5 -5
  262. package/dist/{embedded-voice-MGI7EK7C.js → embedded-voice-GHDRLCFX.js} +5 -5
  263. package/dist/{fleet-decision-row-35S3GAUN.js → fleet-decision-row-DQXRRBT7.js} +2 -2
  264. package/dist/index.js +44 -44
  265. package/dist/interaction.js +5 -5
  266. package/dist/machines.js +3 -3
  267. package/dist/messages/en.d.ts +1 -1
  268. package/dist/messages.js +1 -1
  269. package/dist/{model-policy-picker-menu-KP53N6UJ.js → model-policy-picker-menu-5ALKUJMR.js} +4 -4
  270. package/dist/{platform-activity-row-ISMXR7NJ.js → platform-activity-row-227C764Z.js} +2 -2
  271. package/dist/{queue-surface-implementation-ULW7DDJA.js → queue-surface-implementation-H3NOBXU5.js} +5 -5
  272. package/dist/realtime.js +4 -4
  273. package/dist/{sandbox-files-QYSD4XCZ.js → sandbox-files-5FEOHUMD.js} +6 -6
  274. package/dist/session-app.js +155 -157
  275. package/dist/{session-realtime-JXRLPNUH.js → session-realtime-OHBMODNX.js} +3 -3
  276. package/dist/session-realtime.js +2 -2
  277. package/dist/session-ui.js +26 -26
  278. package/dist/session.js +6 -6
  279. package/dist/sites.js +2 -2
  280. package/dist/terminal.js +2 -2
  281. package/dist/this-computer.js +3 -3
  282. package/dist/timeline/connector-logo-index.d.ts +19 -0
  283. package/dist/{timeline-annotation-markers-OJYKHBEO.js → timeline-annotation-markers-NDHS7MCK.js} +2 -2
  284. package/dist/{timeline-annotation-selection-6OAZLXFU.js → timeline-annotation-selection-DRQSTJOV.js} +2 -2
  285. package/dist/{timeline-annotations-dialog-5M6DP5FX.js → timeline-annotations-dialog-G6J5ZJKN.js} +3 -3
  286. package/dist/timeline-model.d.ts +2 -0
  287. package/dist/timeline-model.js +9 -1
  288. package/dist/ui/list-row.js +1 -1
  289. package/dist/usage.js +2 -2
  290. package/dist/workbench.js +4 -4
  291. package/package.json +4 -4
  292. package/src/app/components/capabilities/native-connect-readiness.tsx +1 -0
  293. package/src/app/components/capabilities/perform-capability-action.test.ts +67 -0
  294. package/src/app/components/capabilities/perform-capability-action.tsx +28 -0
  295. package/src/app/components/command-menu/chat-switcher.tsx +25 -4
  296. package/src/app/components/rail/session-header.test.tsx +73 -2
  297. package/src/app/components/rail/session-header.tsx +20 -12
  298. package/src/app/components/rail/session-shell.tsx +10 -18
  299. package/src/app/components/session/subscription-account-indicator.test.ts +239 -0
  300. package/src/app/components/session/subscription-account-indicator.tsx +361 -0
  301. package/src/app/components/session/subscription-account-menu.tsx +21 -43
  302. package/src/app/components/session/subscription-account.test.ts +314 -0
  303. package/src/app/components/session/subscription-account.ts +354 -0
  304. package/src/app/lib/actions/actions-provider.tsx +11 -0
  305. package/src/app/lib/actions/actions.test.ts +59 -4
  306. package/src/app/lib/actions/chat-switcher.ts +73 -20
  307. package/src/app/lib/drive-connect-return.test.ts +183 -0
  308. package/src/app/lib/drive-connect-return.tsx +116 -0
  309. package/src/app/routes/session.tsx +12 -14
  310. package/src/app/routes/sessions-index.tsx +1 -0
  311. package/src/components/model-policy-picker-menu.tsx +25 -4
  312. package/src/components/model-policy-picker.tsx +2 -0
  313. package/src/components/session-mcp-capability-card.tsx +13 -4
  314. package/src/connection-service-presentation.ts +6 -1
  315. package/src/messages/en.ts +1 -1
  316. package/src/timeline/connector-logo-index.test.ts +46 -0
  317. package/src/timeline/connector-logo-index.ts +68 -0
  318. package/src/timeline-model.ts +6 -0
  319. package/src/ui/list-row.tsx +38 -10
  320. package/styles/compiled.css +13 -0
  321. package/dist/app/components/session/codex-account-indicator.d.ts +0 -9
  322. package/dist/app/components/session/model-account-indicator.d.ts +0 -10
  323. package/dist/app/components/session/model-account-indicator.js +0 -34
  324. package/dist/app/components/session/model-account-usage.d.ts +0 -8
  325. package/dist/app/components/session/model-account-usage.js +0 -8
  326. package/dist/chunk-2K27S6LK.js.map +0 -1
  327. package/dist/chunk-3SMJ2BIQ.js.map +0 -1
  328. package/dist/chunk-6PQBKMUL.js.map +0 -1
  329. package/dist/chunk-7H2D4VNU.js +0 -150
  330. package/dist/chunk-7H2D4VNU.js.map +0 -1
  331. package/dist/chunk-DNLXQUBX.js.map +0 -1
  332. package/dist/chunk-HCCSAECI.js +0 -132
  333. package/dist/chunk-HCCSAECI.js.map +0 -1
  334. package/dist/chunk-IWIMIRV3.js.map +0 -1
  335. package/dist/chunk-M77ZQE5T.js +0 -58
  336. package/dist/chunk-M77ZQE5T.js.map +0 -1
  337. package/dist/chunk-MM7OKOTE.js.map +0 -1
  338. package/dist/chunk-NPDL4W4Y.js.map +0 -1
  339. package/dist/chunk-NWSAF6P5.js.map +0 -1
  340. package/dist/chunk-OHG2UQ2O.js.map +0 -1
  341. package/dist/chunk-PQCFW2KT.js.map +0 -1
  342. package/dist/chunk-S6WJ6TUT.js.map +0 -1
  343. package/dist/chunk-ZWACUOU5.js.map +0 -1
  344. package/dist/session-realtime-JXRLPNUH.js.map +0 -1
  345. package/src/app/components/session/codex-account-indicator.tsx +0 -207
  346. package/src/app/components/session/model-account-indicator.tsx +0 -189
  347. package/src/app/components/session/model-account-usage.test.ts +0 -59
  348. package/src/app/components/session/model-account-usage.tsx +0 -89
  349. /package/dist/app/components/session/{codex-account-indicator.js.map → subscription-account-indicator.js.map} +0 -0
  350. /package/dist/app/{components/session/model-account-indicator.js.map → lib/drive-connect-return.js.map} +0 -0
  351. /package/dist/{app/components/session/model-account-usage.js.map → browser-viewer-VKIAULG7.js.map} +0 -0
  352. /package/dist/{browser-viewer-YQC6BFMS.js.map → chat-interactive-block-JGYSW4KB.js.map} +0 -0
  353. /package/dist/{chunk-WN2MIB4Q.js.map → chunk-22V2QCEM.js.map} +0 -0
  354. /package/dist/{chunk-SJPAKWWP.js.map → chunk-2BISI664.js.map} +0 -0
  355. /package/dist/{chunk-O5UOCMH4.js.map → chunk-2LPJVEWY.js.map} +0 -0
  356. /package/dist/{chunk-JI6WPLZB.js.map → chunk-2SBCOGQN.js.map} +0 -0
  357. /package/dist/{chunk-6JF7C3U3.js.map → chunk-2WERWWSS.js.map} +0 -0
  358. /package/dist/{chunk-4XKT4ENZ.js.map → chunk-2Y3QL3DA.js.map} +0 -0
  359. /package/dist/{chunk-KVMFT6FN.js.map → chunk-3FL3F4WF.js.map} +0 -0
  360. /package/dist/{chunk-4VJOZJDV.js.map → chunk-3JPQAIYJ.js.map} +0 -0
  361. /package/dist/{chunk-27EORKJ4.js.map → chunk-3N4LNBJF.js.map} +0 -0
  362. /package/dist/{chunk-7JZSFJHB.js.map → chunk-3OJU3RUR.js.map} +0 -0
  363. /package/dist/{chunk-NV5OYV3G.js.map → chunk-3U6K36QA.js.map} +0 -0
  364. /package/dist/{chunk-MDRLP5DO.js.map → chunk-3W7ROHN4.js.map} +0 -0
  365. /package/dist/{chunk-3MQM4ZOC.js.map → chunk-3Y6ZR2VN.js.map} +0 -0
  366. /package/dist/{chunk-HXZIX7MR.js.map → chunk-47AB65IO.js.map} +0 -0
  367. /package/dist/{chunk-DNYD7AUG.js.map → chunk-4BOLIZ7Y.js.map} +0 -0
  368. /package/dist/{chunk-4XO346BW.js.map → chunk-4FBJFQFU.js.map} +0 -0
  369. /package/dist/{chunk-BWKZEW6K.js.map → chunk-4T3E56NT.js.map} +0 -0
  370. /package/dist/{chunk-4PC7LIQK.js.map → chunk-4ZMUWRFF.js.map} +0 -0
  371. /package/dist/{chunk-TM2ZLY3Q.js.map → chunk-5OLFYSVG.js.map} +0 -0
  372. /package/dist/{chunk-D4QOZXPM.js.map → chunk-5WBRM7V4.js.map} +0 -0
  373. /package/dist/{chunk-GXK4CUAB.js.map → chunk-5WQKTT5Z.js.map} +0 -0
  374. /package/dist/{chunk-6N7W3TOV.js.map → chunk-63BPMTBM.js.map} +0 -0
  375. /package/dist/{chunk-R5YZ3ROM.js.map → chunk-63PQKTTT.js.map} +0 -0
  376. /package/dist/{chunk-WLZVXCPZ.js.map → chunk-6GO6WN22.js.map} +0 -0
  377. /package/dist/{chunk-XDCZBOEY.js.map → chunk-6LGBGPBU.js.map} +0 -0
  378. /package/dist/{chunk-FMATVQFW.js.map → chunk-6XXMG5YP.js.map} +0 -0
  379. /package/dist/{chunk-M5E3CKSE.js.map → chunk-7QWWE5XL.js.map} +0 -0
  380. /package/dist/{chunk-RABKQFTA.js.map → chunk-A2HCAMRH.js.map} +0 -0
  381. /package/dist/{chunk-6K3WNTRI.js.map → chunk-AODISQO6.js.map} +0 -0
  382. /package/dist/{chunk-C6JL6PBL.js.map → chunk-AQDRH6VF.js.map} +0 -0
  383. /package/dist/{chunk-72XEFLMD.js.map → chunk-ASWINLSW.js.map} +0 -0
  384. /package/dist/{chunk-ZPZYYSHW.js.map → chunk-BVMDON5T.js.map} +0 -0
  385. /package/dist/{chunk-EURDMOXZ.js.map → chunk-BYJZUCOQ.js.map} +0 -0
  386. /package/dist/{chunk-BQWUXC55.js.map → chunk-CAVAVT5S.js.map} +0 -0
  387. /package/dist/{chunk-BPD4IJBG.js.map → chunk-CG6CJMIS.js.map} +0 -0
  388. /package/dist/{chunk-B254JG72.js.map → chunk-CQX24PT7.js.map} +0 -0
  389. /package/dist/{chunk-HJTTDLF6.js.map → chunk-CY3NO4JN.js.map} +0 -0
  390. /package/dist/{chunk-OLUG4IFF.js.map → chunk-D2RLPRSG.js.map} +0 -0
  391. /package/dist/{chunk-74NIHMAT.js.map → chunk-D653JH6D.js.map} +0 -0
  392. /package/dist/{chunk-2LUO22FR.js.map → chunk-DAI5CTQF.js.map} +0 -0
  393. /package/dist/{chunk-HTKVYEVB.js.map → chunk-DBM4ZXDN.js.map} +0 -0
  394. /package/dist/{chunk-MB2DTUEG.js.map → chunk-DNYKLU3K.js.map} +0 -0
  395. /package/dist/{chunk-26P7B46A.js.map → chunk-DXYARXK3.js.map} +0 -0
  396. /package/dist/{chunk-JQKURO3S.js.map → chunk-E4MQRQ6Z.js.map} +0 -0
  397. /package/dist/{chunk-KW53DBY7.js.map → chunk-EBW2E32G.js.map} +0 -0
  398. /package/dist/{chunk-O73DRA77.js.map → chunk-ENKBO7FC.js.map} +0 -0
  399. /package/dist/{chunk-FVCLVASD.js.map → chunk-FI3KRVOF.js.map} +0 -0
  400. /package/dist/{chunk-WWM7WVD2.js.map → chunk-GABFKHQE.js.map} +0 -0
  401. /package/dist/{chunk-32HA7DXJ.js.map → chunk-GNBRB3DH.js.map} +0 -0
  402. /package/dist/{chunk-CEQUNJJY.js.map → chunk-HPALK6LD.js.map} +0 -0
  403. /package/dist/{chunk-LR7U3HCD.js.map → chunk-HPSATFLQ.js.map} +0 -0
  404. /package/dist/{chunk-O4AWXTZQ.js.map → chunk-HUYLQW75.js.map} +0 -0
  405. /package/dist/{chunk-ZZUKJ5HQ.js.map → chunk-IG2ERMSB.js.map} +0 -0
  406. /package/dist/{chunk-2SZ3KC5S.js.map → chunk-IMKIIS4Y.js.map} +0 -0
  407. /package/dist/{chunk-CA64OZLZ.js.map → chunk-IRXPXDY5.js.map} +0 -0
  408. /package/dist/{chunk-I57HZMMA.js.map → chunk-IXIE3H43.js.map} +0 -0
  409. /package/dist/{chunk-OWJD3V4V.js.map → chunk-J3YQ3554.js.map} +0 -0
  410. /package/dist/{chunk-BCFHMBMK.js.map → chunk-J5HZBHU4.js.map} +0 -0
  411. /package/dist/{chunk-UHMMRQGU.js.map → chunk-KD7GNHOP.js.map} +0 -0
  412. /package/dist/{chunk-X7Z4B5GK.js.map → chunk-KPL7EXS6.js.map} +0 -0
  413. /package/dist/{chunk-GTR2WUBA.js.map → chunk-KX6M5CJI.js.map} +0 -0
  414. /package/dist/{chunk-CYO5HL4C.js.map → chunk-KYL2BPS2.js.map} +0 -0
  415. /package/dist/{chunk-QLDMF7MH.js.map → chunk-LC5HE7PZ.js.map} +0 -0
  416. /package/dist/{chunk-4ICPJZAB.js.map → chunk-LMYSYG6U.js.map} +0 -0
  417. /package/dist/{chunk-3ZZOE4SN.js.map → chunk-LOCDYMD6.js.map} +0 -0
  418. /package/dist/{chunk-DNWBLT4U.js.map → chunk-MASBMJ2M.js.map} +0 -0
  419. /package/dist/{chunk-NZMQDUOD.js.map → chunk-MU6HYDNF.js.map} +0 -0
  420. /package/dist/{chunk-GMVKCEBG.js.map → chunk-MY6QEDZ2.js.map} +0 -0
  421. /package/dist/{chunk-SK57RWSA.js.map → chunk-MZGKQ5WV.js.map} +0 -0
  422. /package/dist/{chunk-HSQRYJ4P.js.map → chunk-N52SFH2K.js.map} +0 -0
  423. /package/dist/{chunk-MY6VZVCP.js.map → chunk-OAJX5CEL.js.map} +0 -0
  424. /package/dist/{chunk-FH75NNLB.js.map → chunk-OZ5NAXCP.js.map} +0 -0
  425. /package/dist/{chunk-PEGNFZ3F.js.map → chunk-P5S777YI.js.map} +0 -0
  426. /package/dist/{chunk-LY6727IF.js.map → chunk-PPSVFNEA.js.map} +0 -0
  427. /package/dist/{chunk-K4FDZVMQ.js.map → chunk-PR42VIVY.js.map} +0 -0
  428. /package/dist/{chunk-I74J2UZ2.js.map → chunk-PWMZCW4H.js.map} +0 -0
  429. /package/dist/{chunk-6YDXUE5H.js.map → chunk-PYA2O3WS.js.map} +0 -0
  430. /package/dist/{chunk-GUIZDI5T.js.map → chunk-Q2PA4KZS.js.map} +0 -0
  431. /package/dist/{chunk-JJ7VMIEQ.js.map → chunk-QCANSTLU.js.map} +0 -0
  432. /package/dist/{chunk-DZH2HTTB.js.map → chunk-R27GW5WV.js.map} +0 -0
  433. /package/dist/{chunk-DAQG3C4W.js.map → chunk-R6JY22SG.js.map} +0 -0
  434. /package/dist/{chunk-7OIBLMDP.js.map → chunk-RAQISW42.js.map} +0 -0
  435. /package/dist/{chunk-CPKD2YGK.js.map → chunk-RNU5CUPI.js.map} +0 -0
  436. /package/dist/{chunk-467VRP4E.js.map → chunk-RO5YZQHZ.js.map} +0 -0
  437. /package/dist/{chunk-SSZROOOI.js.map → chunk-ROLXYGT5.js.map} +0 -0
  438. /package/dist/{chunk-6VITVQ23.js.map → chunk-SHJ5SQOW.js.map} +0 -0
  439. /package/dist/{chunk-F4B2YYCN.js.map → chunk-TAWFXRXB.js.map} +0 -0
  440. /package/dist/{chunk-G3FU2IF7.js.map → chunk-TB7UW5BB.js.map} +0 -0
  441. /package/dist/{chunk-NF6Q4XTN.js.map → chunk-TQF2I5SY.js.map} +0 -0
  442. /package/dist/{chunk-M7A226EQ.js.map → chunk-UL44R3W2.js.map} +0 -0
  443. /package/dist/{chunk-GTR5NLVP.js.map → chunk-VCRQPLKS.js.map} +0 -0
  444. /package/dist/{chunk-MHIYYHCL.js.map → chunk-VJU3ET7N.js.map} +0 -0
  445. /package/dist/{chunk-RXMIXKB3.js.map → chunk-WGJUARF3.js.map} +0 -0
  446. /package/dist/{chunk-LG67ZNRU.js.map → chunk-WK2ZCTUF.js.map} +0 -0
  447. /package/dist/{chunk-XGE75VPK.js.map → chunk-WNARM3LY.js.map} +0 -0
  448. /package/dist/{chunk-RNBH5TSM.js.map → chunk-WQ2RTQYO.js.map} +0 -0
  449. /package/dist/{chunk-W57QFLQD.js.map → chunk-X6JIGNLI.js.map} +0 -0
  450. /package/dist/{chunk-CKA4SJRK.js.map → chunk-XIQDCQX7.js.map} +0 -0
  451. /package/dist/{chunk-AJ7PY4FJ.js.map → chunk-Y3GCG6CA.js.map} +0 -0
  452. /package/dist/{chunk-ZRB4M3GG.js.map → chunk-YC5KH7ZB.js.map} +0 -0
  453. /package/dist/{chunk-4RY64KQY.js.map → chunk-YJUKLLNP.js.map} +0 -0
  454. /package/dist/{chunk-24POWVUY.js.map → chunk-ZDJNRRVS.js.map} +0 -0
  455. /package/dist/{chunk-M7YEZR6X.js.map → chunk-ZITMDA4C.js.map} +0 -0
  456. /package/dist/{chat-interactive-block-Z22YHDRU.js.map → computer-viewer-6CRLWQHP.js.map} +0 -0
  457. /package/dist/{computer-viewer-BWVNDFTO.js.map → embedded-new-voice-KILV3RYM.js.map} +0 -0
  458. /package/dist/{embedded-new-voice-GBXSZ3TO.js.map → embedded-voice-GHDRLCFX.js.map} +0 -0
  459. /package/dist/{fleet-decision-row-35S3GAUN.js.map → fleet-decision-row-DQXRRBT7.js.map} +0 -0
  460. /package/dist/{embedded-voice-MGI7EK7C.js.map → model-policy-picker-menu-5ALKUJMR.js.map} +0 -0
  461. /package/dist/{platform-activity-row-ISMXR7NJ.js.map → platform-activity-row-227C764Z.js.map} +0 -0
  462. /package/dist/{queue-surface-implementation-ULW7DDJA.js.map → queue-surface-implementation-H3NOBXU5.js.map} +0 -0
  463. /package/dist/{model-policy-picker-menu-KP53N6UJ.js.map → sandbox-files-5FEOHUMD.js.map} +0 -0
  464. /package/dist/{sandbox-files-QYSD4XCZ.js.map → session-realtime-OHBMODNX.js.map} +0 -0
  465. /package/dist/{timeline-annotation-markers-OJYKHBEO.js.map → timeline-annotation-markers-NDHS7MCK.js.map} +0 -0
  466. /package/dist/{timeline-annotation-selection-6OAZLXFU.js.map → timeline-annotation-selection-DRQSTJOV.js.map} +0 -0
  467. /package/dist/{timeline-annotations-dialog-5M6DP5FX.js.map → timeline-annotations-dialog-G6J5ZJKN.js.map} +0 -0
@@ -0,0 +1,314 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ showsSubscriptionAccountMenu,
4
+ subscriptionAccountMenuContent,
5
+ subscriptionAccountProvider,
6
+ subscriptionAccountUsage,
7
+ type SubscriptionAccount,
8
+ type SubscriptionAccountState,
9
+ type SubscriptionAccountUsage,
10
+ } from "./subscription-account";
11
+
12
+ const snapshot = (
13
+ windows: SubscriptionAccountUsage["windows"],
14
+ extra: Partial<SubscriptionAccountUsage> = {},
15
+ ): SubscriptionAccountUsage => ({
16
+ windows,
17
+ observedAt: null,
18
+ exhaustedUntil: null,
19
+ exhaustedKind: null,
20
+ ...extra,
21
+ });
22
+
23
+ function account(id: string, extra: Partial<SubscriptionAccount> = {}): SubscriptionAccount {
24
+ return {
25
+ id,
26
+ label: `Acme ${id}`,
27
+ email: `${id}@example.test`,
28
+ plan: "Pro",
29
+ source: "organization",
30
+ status: "active",
31
+ refusal: null,
32
+ choosable: true,
33
+ usage: snapshot(
34
+ [
35
+ { id: "five_hour", usedPercent: 36, resetsAt: null, status: "ok" },
36
+ { id: "seven_day", usedPercent: 62, resetsAt: null, status: "ok" },
37
+ ],
38
+ { observedAt: "2026-10-22T16:50:00Z" },
39
+ ),
40
+ ...extra,
41
+ };
42
+ }
43
+
44
+ function state(extra: Partial<SubscriptionAccountState> = {}): SubscriptionAccountState {
45
+ const accounts = [
46
+ account("a"),
47
+ account("b", { source: "workspace" }),
48
+ account("c", { status: "needs_relogin", choosable: false, refusal: "inactive", usage: null }),
49
+ ];
50
+ return {
51
+ provider: "claude",
52
+ loading: false,
53
+ unavailable: false,
54
+ accounts,
55
+ current: accounts[0]!,
56
+ currentHidden: false,
57
+ serving: false,
58
+ automatic: true,
59
+ chosenId: null,
60
+ waiting: false,
61
+ usageCheck: "idle",
62
+ choosing: false,
63
+ choosingTarget: undefined,
64
+ appliedTo: null,
65
+ mutationError: null,
66
+ ...extra,
67
+ };
68
+ }
69
+
70
+ describe("one menu for every subscription provider", () => {
71
+ test("Codex and Claude differ only in the provider name", () => {
72
+ const claude = subscriptionAccountMenuContent(state({ provider: "claude" }));
73
+ const codex = subscriptionAccountMenuContent(state({ provider: "codex" }));
74
+ const neutral = (value: unknown) => JSON.stringify(value).replaceAll(/Claude|Codex/g, "X");
75
+ expect(neutral(codex)).toBe(neutral(claude));
76
+ expect(claude.ariaLabel).toBe("Claude account · Acme a");
77
+ expect(codex.ariaLabel).toBe("Codex account · Acme a");
78
+ });
79
+
80
+ test("the current account shows its plan, owner, email and usage the same way", () => {
81
+ const content = subscriptionAccountMenuContent(state());
82
+ expect(content.selectionLabel).toBe("Current account");
83
+ expect(content.identity).toBe("a@example.test");
84
+ expect(content.metadata).toBe("Pro · Organization");
85
+ expect(content.usage.windows?.map((window) => [window.label, window.usedPercent])).toEqual([
86
+ ["5h", 36],
87
+ ["Week", 62],
88
+ ]);
89
+ expect(content.usage.note).toStartWith("Updated ");
90
+ expect(content.automaticLabel).toBe("Automatic");
91
+ expect(content.actionLabel).toBe("Use for next turn");
92
+ // Other accounts summarize each window; the current one is shown above.
93
+ expect(content.accounts.map((row) => row.usageSummary)).toEqual([
94
+ undefined,
95
+ "5h 36% · Week 62% used",
96
+ undefined,
97
+ ]);
98
+ expect(content.accounts[2]).toMatchObject({ status: "Needs reconnect", disabled: true });
99
+ expect(content.accounts[1]?.detail).toBe("Acme b · b@example.test · Pro · This workspace");
100
+ });
101
+
102
+ test("a turn waiting for capacity offers a retry for any provider", () => {
103
+ for (const provider of ["codex", "claude"]) {
104
+ const content = subscriptionAccountMenuContent(state({ provider, waiting: true }));
105
+ expect(content.selectionLabel).toBe("Limit reached");
106
+ expect(content.actionLabel).toBe("Retry with");
107
+ expect(content.tone).toBe("warn");
108
+ expect(content.note).toBe("Changing accounts retries the waiting turn.");
109
+ }
110
+ });
111
+
112
+ test("a chosen account this person cannot see keeps a way back to Automatic", () => {
113
+ const content = subscriptionAccountMenuContent(
114
+ state({ current: null, currentHidden: true, automatic: false, chosenId: null }),
115
+ );
116
+ expect(content.displayName).toBe("Account unavailable");
117
+ expect(content.selectionLabel).toBe("Chosen account");
118
+ expect(content.usage.message).toBe("Account usage hidden");
119
+ expect(content.automaticSelected).toBe(false);
120
+ expect(content.selectedId).toBeNull();
121
+ });
122
+
123
+ test("an explicit choice is checked and named as chosen", () => {
124
+ const content = subscriptionAccountMenuContent(state({ automatic: false, chosenId: "a" }));
125
+ expect(content.selectionLabel).toBe("Chosen account");
126
+ expect(content.selectedId).toBe("a");
127
+ expect(content.automaticSelected).toBe(false);
128
+ });
129
+
130
+ test("tone flags reconnects and windows near their limit", () => {
131
+ expect(
132
+ subscriptionAccountMenuContent(state({ current: account("x", { status: "needs_relogin" }) }))
133
+ .tone,
134
+ ).toBe("bad");
135
+ expect(
136
+ subscriptionAccountMenuContent(
137
+ state({
138
+ current: account("x", {
139
+ usage: snapshot([{ id: "five_hour", usedPercent: 95, resetsAt: null, status: "ok" }]),
140
+ }),
141
+ }),
142
+ ).tone,
143
+ ).toBe("warn");
144
+ expect(subscriptionAccountMenuContent(state()).tone).toBe("ok");
145
+ });
146
+
147
+ test("empty and failed reads say so in the same words", () => {
148
+ const empty = subscriptionAccountMenuContent(state({ accounts: [], current: null }));
149
+ expect(empty.emptyMessage).toBe("No accounts available.");
150
+ expect(empty.displayName).toBe("Automatic");
151
+ expect(empty.metadata).toBe("Claude subscription");
152
+ const failed = subscriptionAccountMenuContent(
153
+ state({ accounts: [], current: null, unavailable: true }),
154
+ );
155
+ expect(failed.emptyMessage).toBe("Accounts unavailable.");
156
+ // A failed read does not know the selection, so nothing claims Automatic.
157
+ expect(failed.displayName).toBe("Accounts unavailable");
158
+ expect(failed.selectionLabel).toBe("Account selection");
159
+ expect(failed.automaticSelected).toBe(false);
160
+ expect(failed.selectedId).toBeNull();
161
+ expect(failed.ariaLabel).toBe("Claude account · Accounts unavailable");
162
+ expect(subscriptionAccountMenuContent(state({ mutationError: "busy" })).error).toBe(
163
+ "Couldn't switch: busy",
164
+ );
165
+ });
166
+
167
+ test("a failed check on the current account keeps the other accounts' readings", () => {
168
+ const failed = subscriptionAccountMenuContent(state({ usageCheck: "failed" }));
169
+ expect(failed.accounts.map((row) => row.usageSummary)).toEqual([
170
+ undefined,
171
+ "5h 36% · Week 62% used",
172
+ undefined,
173
+ ]);
174
+ });
175
+
176
+ test("the serving account is the current account, even when another one is chosen", () => {
177
+ const hidden = subscriptionAccountMenuContent(
178
+ state({ current: null, currentHidden: true, serving: true, automatic: false, chosenId: "b" }),
179
+ );
180
+ expect(hidden.selectionLabel).toBe("Current account");
181
+ expect(hidden.displayName).toBe("Account unavailable");
182
+ expect(hidden.selectedId).toBe("b");
183
+ const visible = subscriptionAccountMenuContent(
184
+ state({ serving: true, automatic: false, chosenId: "a" }),
185
+ );
186
+ expect(visible.selectionLabel).toBe("Current account");
187
+ });
188
+
189
+ test("a chip near its limit says so to assistive technology", () => {
190
+ const near = subscriptionAccountMenuContent(
191
+ state({
192
+ current: account("a", {
193
+ usage: snapshot([
194
+ { id: "five_hour", usedPercent: 95, resetsAt: null, status: "warning" },
195
+ ]),
196
+ }),
197
+ }),
198
+ );
199
+ expect(near.ariaLabel).toBe("Claude account · Acme a · Near its limit");
200
+ });
201
+
202
+ test("with nothing to choose or show, only the provider mark remains", () => {
203
+ expect(showsSubscriptionAccountMenu(state({ accounts: [], current: null }))).toBe(false);
204
+ expect(showsSubscriptionAccountMenu(state())).toBe(true);
205
+ for (const extra of [
206
+ { unavailable: true },
207
+ { currentHidden: true },
208
+ { waiting: true },
209
+ { automatic: false },
210
+ ]) {
211
+ expect(showsSubscriptionAccountMenu(state({ accounts: [], current: null, ...extra }))).toBe(
212
+ true,
213
+ );
214
+ }
215
+ });
216
+
217
+ test("a live check hides readings until it answers", () => {
218
+ const checking = subscriptionAccountMenuContent(state({ usageCheck: "checking" }));
219
+ expect(checking.usage).toEqual({ checking: true });
220
+ expect(checking.accounts.every((row) => row.usageSummary === undefined)).toBe(true);
221
+ expect(subscriptionAccountMenuContent(state({ usageCheck: "failed" })).usage).toEqual({
222
+ unavailable: true,
223
+ });
224
+ });
225
+ });
226
+
227
+ describe("usage readings", () => {
228
+ test("a limit whose end has passed no longer reads as reached", () => {
229
+ const ended = snapshot([], {
230
+ exhaustedUntil: "2026-10-22T10:00:00Z",
231
+ exhaustedKind: "quota",
232
+ });
233
+ expect(subscriptionAccountUsage(ended, Date.parse("2026-10-22T11:00:00Z")).message).toBe(
234
+ "No usage reading yet",
235
+ );
236
+ expect(subscriptionAccountUsage(ended, Date.parse("2026-10-22T09:00:00Z")).message).toStartWith(
237
+ "Limit reached until ",
238
+ );
239
+ });
240
+
241
+ test("missing readings and empty snapshots say there is no reading yet", () => {
242
+ expect(subscriptionAccountUsage(undefined).message).toBe("No usage reading yet");
243
+ expect(subscriptionAccountUsage(null).message).toBe("No usage reading yet");
244
+ const empty = subscriptionAccountUsage(snapshot([]));
245
+ expect(empty).toMatchObject({ message: "No usage reading yet", windows: [] });
246
+ expect(empty.note).toBeUndefined();
247
+ });
248
+
249
+ test("partial and unknown windows are never completed, aggregated or given a made-up percent", () => {
250
+ const view = subscriptionAccountUsage(
251
+ snapshot([
252
+ { id: "seven_day_opus", usedPercent: null, resetsAt: null, status: "unknown" },
253
+ { id: "overage", usedPercent: 125, resetsAt: null, status: "unknown" },
254
+ { id: "custom", usedPercent: 8, resetsAt: null, status: "warning" },
255
+ ]),
256
+ );
257
+ expect(view.windows?.map((window) => window.usedPercent)).toEqual([null, 125, 8]);
258
+ expect(view.windows?.map((window) => window.label)).toEqual([
259
+ "Week · Opus",
260
+ "Overage",
261
+ "custom",
262
+ ]);
263
+ expect(view.summary).toBe("Week · Opus unknown · Overage 125% · custom 8% used");
264
+ expect(view.windows?.[0]?.detail).toContain("Status unknown");
265
+ expect(view.windows?.[2]?.detail).toContain("Near its limit");
266
+ });
267
+
268
+ test("neutral primary/secondary ids do not claim a window length", () => {
269
+ const view = subscriptionAccountUsage(
270
+ snapshot([{ id: "primary", usedPercent: 10, resetsAt: null, status: "ok" }]),
271
+ );
272
+ expect(view.windows?.[0]?.label).toBe("Primary");
273
+ });
274
+
275
+ test("an account-wide limit without windows names when it ends", () => {
276
+ const view = subscriptionAccountUsage(
277
+ snapshot([], { exhaustedKind: "rate_limit", exhaustedUntil: "2020-01-01T09:00:00Z" }),
278
+ Date.parse("2020-01-01T08:00:00Z"),
279
+ );
280
+ expect(view.message).toStartWith("Limit reached until ");
281
+ expect(view.windows).toEqual([]);
282
+ });
283
+
284
+ test("a past reset keeps the reading but says it may be outdated", () => {
285
+ const value = snapshot([
286
+ { id: "five_hour", usedPercent: 100, resetsAt: "2026-10-12T12:00:00Z", status: "exhausted" },
287
+ ]);
288
+ const before = subscriptionAccountUsage(value, Date.parse("2026-10-12T11:00:00Z"));
289
+ const after = subscriptionAccountUsage(value, Date.parse("2026-10-12T13:00:00Z"));
290
+ expect(before.windows?.[0]?.detail).toContain("Resets ");
291
+ expect(after.windows?.[0]?.detail).toContain("Reset passed; usage may be outdated");
292
+ expect(after.windows?.[0]?.detail).toContain("Limit reached");
293
+ expect(after.windows?.[0]?.usedPercent).toBe(100);
294
+ });
295
+ });
296
+
297
+ describe("which subscription a chat offers accounts for", () => {
298
+ test("Codex product models, then the catalog's connected subscription", () => {
299
+ expect(subscriptionAccountProvider("codex/gpt-6-astra", undefined)).toBe("codex");
300
+ expect(
301
+ subscriptionAccountProvider("organization-claude-subscription/claude-opus-5-5", {
302
+ kind: "connected_subscription",
303
+ provider: "claude",
304
+ }),
305
+ ).toBe("claude");
306
+ expect(
307
+ subscriptionAccountProvider("supergrok/grok-4.6", {
308
+ kind: "connected_subscription",
309
+ provider: "xai",
310
+ }),
311
+ ).toBe("xai");
312
+ expect(subscriptionAccountProvider("gpt-5.6-sol", undefined)).toBeNull();
313
+ });
314
+ });
@@ -0,0 +1,354 @@
1
+ // One provider-neutral shape for a chat's subscription account (Codex, Claude,
2
+ // SuperGrok) and its projection onto the account menu. Data sources normalize
3
+ // into `SubscriptionAccountState`; everything a person sees is decided here,
4
+ // so every provider shows the same structure and copy. Only names, marks and
5
+ // plan labels differ.
6
+ import type { ClientModel, ModelAccountChoiceRefusal } from "@opengeni/sdk";
7
+ import { isCodexProductModel } from "../../lib/session-model";
8
+ import type { BillingClass } from "../billing-class-mark";
9
+ import type {
10
+ SubscriptionAccountMenuRow,
11
+ SubscriptionAccountMenuUsage,
12
+ SubscriptionAccountUsageWindow,
13
+ } from "./subscription-account-menu";
14
+
15
+ export type SubscriptionAccountUsage = {
16
+ /** When the reading was taken; null when unknown. */
17
+ observedAt: string | null;
18
+ windows: Array<{
19
+ id: string;
20
+ /** Null means unknown; values over 100 can represent overage. */
21
+ usedPercent: number | null;
22
+ resetsAt: string | null;
23
+ status: "ok" | "warning" | "exhausted" | "unknown";
24
+ }>;
25
+ exhaustedUntil: string | null;
26
+ exhaustedKind: "quota" | "rate_limit" | null;
27
+ };
28
+
29
+ export type SubscriptionAccount = {
30
+ id: string;
31
+ label: string | null;
32
+ email: string | null;
33
+ plan: string | null;
34
+ /** Who the account belongs to; null when the source does not say. */
35
+ source: "personal" | "workspace" | "organization" | null;
36
+ /** The provider's account status (`active`, `needs_relogin`, …). */
37
+ status: string;
38
+ refusal: ModelAccountChoiceRefusal | null;
39
+ choosable: boolean;
40
+ /** Undefined: not reported. Null: no reading yet. */
41
+ usage: SubscriptionAccountUsage | null | undefined;
42
+ };
43
+
44
+ export type SubscriptionAccountState = {
45
+ /** `codex`, `claude`, `xai`, … */
46
+ provider: string;
47
+ /** True until the first read settles. */
48
+ loading: boolean;
49
+ /** The accounts could not be read. */
50
+ unavailable: boolean;
51
+ /** Accounts this person can pick for the next turn. */
52
+ accounts: SubscriptionAccount[];
53
+ /** The account the chat runs on (or will next), when this person can see it. */
54
+ current: SubscriptionAccount | null;
55
+ /** The chat runs on a chosen or serving account this person cannot see. */
56
+ currentHidden: boolean;
57
+ /** `current`/`currentHidden` is the account serving the chat's latest turn. */
58
+ serving: boolean;
59
+ /** Automatic selection, not a person's explicit choice. */
60
+ automatic: boolean;
61
+ /** The explicitly chosen account, when this person can see it. */
62
+ chosenId: string | null;
63
+ /** A turn waits for capacity on the current account. */
64
+ waiting: boolean;
65
+ /**
66
+ * A live usage check, for sources that offer one: `checking` until its
67
+ * first reading arrives, `failed` when it produced none for this account.
68
+ */
69
+ usageCheck: "idle" | "checking" | "failed";
70
+ choosing: boolean;
71
+ /** The in-flight target (`null` is automatic; `undefined` when idle). */
72
+ choosingTarget: string | null | undefined;
73
+ appliedTo: "waiting_turn" | "next_turn" | null;
74
+ mutationError: string | null;
75
+ };
76
+
77
+ const PROVIDER: Record<string, { name: string; billingClass: BillingClass }> = {
78
+ codex: { name: "Codex", billingClass: "codex_subscription" },
79
+ claude: { name: "Claude", billingClass: "claude_subscription" },
80
+ xai: { name: "SuperGrok", billingClass: "supergrok_subscription" },
81
+ };
82
+
83
+ export function subscriptionProviderPresentation(provider: string): {
84
+ name: string;
85
+ billingClass: BillingClass;
86
+ } {
87
+ return PROVIDER[provider] ?? { name: "Subscription", billingClass: "external" };
88
+ }
89
+
90
+ // Presentation labels only. Preserve every supplied window; never combine
91
+ // family-specific, overage or partial windows into an account capacity score.
92
+ const WINDOW_LABEL: Record<string, string> = {
93
+ // Neutral ids: they do not say how long the window is.
94
+ primary: "Primary",
95
+ secondary: "Secondary",
96
+ five_hour: "5h",
97
+ seven_day: "Week",
98
+ seven_day_opus: "Week · Opus",
99
+ seven_day_sonnet: "Week · Sonnet",
100
+ overage: "Overage",
101
+ billing: "Billing",
102
+ };
103
+
104
+ const REFUSAL_COPY: Record<ModelAccountChoiceRefusal, string> = {
105
+ inactive: "Unavailable",
106
+ personal_not_allowed: "Turned off here",
107
+ personal_not_owner_work: "Not available for this chat",
108
+ session_has_no_owner: "Needs an owner",
109
+ };
110
+
111
+ const STATUS_COPY: Record<string, string> = {
112
+ needs_relogin: "Needs reconnect",
113
+ error: "Error",
114
+ disabled: "Disabled",
115
+ revoked: "Revoked",
116
+ expired: "Expired",
117
+ suspended: "Suspended",
118
+ paused: "Paused",
119
+ inactive: "Inactive",
120
+ limit_reached: "Limit reached",
121
+ };
122
+
123
+ function time(value: string | null): string | null {
124
+ if (!value || !Number.isFinite(Date.parse(value))) return null;
125
+ return new Date(value).toLocaleString(undefined, {
126
+ month: "short",
127
+ day: "numeric",
128
+ hour: "numeric",
129
+ minute: "2-digit",
130
+ });
131
+ }
132
+
133
+ export function accountLabel(account: SubscriptionAccount): string {
134
+ return account.label ?? account.email ?? account.plan ?? "Account";
135
+ }
136
+
137
+ export function accountStatus(account: SubscriptionAccount): string | null {
138
+ // Inactive is a broad refusal; retain the actual status when it explains why.
139
+ if (account.refusal && account.refusal !== "inactive") return REFUSAL_COPY[account.refusal];
140
+ if (account.status !== "active") return STATUS_COPY[account.status] ?? account.status;
141
+ return account.refusal ? REFUSAL_COPY[account.refusal] : null;
142
+ }
143
+
144
+ function accountSource(account: SubscriptionAccount): string | null {
145
+ return account.source === "personal"
146
+ ? "Your account"
147
+ : account.source === "workspace"
148
+ ? "This workspace"
149
+ : account.source === "organization"
150
+ ? "Organization"
151
+ : null;
152
+ }
153
+
154
+ function windowLabel(id: string): string {
155
+ return WINDOW_LABEL[id] ?? id;
156
+ }
157
+
158
+ /** The reading as usage rows: percentage used, with when each window resets. */
159
+ export function subscriptionAccountUsage(
160
+ usage: SubscriptionAccountUsage | null | undefined,
161
+ now = Date.now(),
162
+ ): {
163
+ windows?: SubscriptionAccountUsageWindow[];
164
+ message?: string;
165
+ note?: string;
166
+ summary?: string;
167
+ } {
168
+ if (!usage) return { message: "No usage reading yet" };
169
+ const observed = time(usage.observedAt);
170
+ const windows = usage.windows.map((window) => {
171
+ const reset = time(window.resetsAt);
172
+ const past = reset !== null && Date.parse(window.resetsAt!) <= now;
173
+ return {
174
+ label: windowLabel(window.id),
175
+ usedPercent: window.usedPercent,
176
+ detail: [
177
+ window.status === "unknown"
178
+ ? "Status unknown"
179
+ : window.status === "warning"
180
+ ? "Near its limit"
181
+ : window.status === "exhausted"
182
+ ? "Limit reached"
183
+ : null,
184
+ past ? "Reset passed; usage may be outdated" : reset ? `Resets ${reset}` : null,
185
+ ]
186
+ .filter(Boolean)
187
+ .join(" · "),
188
+ };
189
+ });
190
+ const exhaustedTime = time(usage.exhaustedUntil);
191
+ // A limit whose end has passed no longer applies.
192
+ const exhausted =
193
+ usage.exhaustedKind !== null &&
194
+ !(usage.exhaustedUntil && Date.parse(usage.exhaustedUntil) <= now);
195
+ return {
196
+ windows,
197
+ ...(windows.length === 0
198
+ ? {
199
+ message: exhausted
200
+ ? `Limit reached${exhaustedTime ? ` until ${exhaustedTime}` : ""}`
201
+ : "No usage reading yet",
202
+ }
203
+ : {}),
204
+ ...(observed ? { note: `Updated ${observed}` } : {}),
205
+ ...(windows.length > 0
206
+ ? {
207
+ summary: `${windows
208
+ .map(
209
+ (window) =>
210
+ `${window.label} ${window.usedPercent === null ? "unknown" : `${Math.round(window.usedPercent)}%`}`,
211
+ )
212
+ .join(" · ")} used`,
213
+ }
214
+ : {}),
215
+ };
216
+ }
217
+
218
+ function nearLimit(usage: SubscriptionAccountUsage | null | undefined): boolean {
219
+ return (usage?.windows ?? []).some(
220
+ (window) =>
221
+ window.status === "warning" ||
222
+ window.status === "exhausted" ||
223
+ (window.usedPercent !== null && window.usedPercent >= 90),
224
+ );
225
+ }
226
+
227
+ /**
228
+ * Whether there is anything to show or choose. A chat whose provider offers
229
+ * no accounts to this person, with nothing chosen, serving or waiting, shows
230
+ * the provider's mark alone instead of an empty menu.
231
+ */
232
+ export function showsSubscriptionAccountMenu(state: SubscriptionAccountState): boolean {
233
+ return (
234
+ state.unavailable ||
235
+ state.accounts.length > 0 ||
236
+ state.current !== null ||
237
+ state.currentHidden ||
238
+ state.waiting ||
239
+ !state.automatic
240
+ );
241
+ }
242
+
243
+ /**
244
+ * The subscription whose accounts a chat's header offers, or null for a chat
245
+ * that does not run on a subscription. Codex product models are Codex
246
+ * subscriptions even before the catalog lists them.
247
+ */
248
+ export function subscriptionAccountProvider(
249
+ modelId: string,
250
+ credentialSource: ClientModel["credentialSource"],
251
+ ): string | null {
252
+ if (isCodexProductModel(modelId)) return "codex";
253
+ return credentialSource?.kind === "connected_subscription" ? credentialSource.provider : null;
254
+ }
255
+
256
+ /** Everything the account menu shows, from the neutral state. */
257
+ export function subscriptionAccountMenuContent(state: SubscriptionAccountState) {
258
+ const { name } = subscriptionProviderPresentation(state.provider);
259
+ const current = state.current;
260
+ // A failed read does not know the selection; never claim Automatic for it.
261
+ const unknown = state.unavailable && !current && !state.currentHidden;
262
+ const displayName = current
263
+ ? accountLabel(current)
264
+ : state.currentHidden
265
+ ? "Account unavailable"
266
+ : unknown
267
+ ? "Accounts unavailable"
268
+ : "Automatic";
269
+ const status = current ? accountStatus(current) : null;
270
+ const selectionLabel = state.waiting
271
+ ? "Limit reached"
272
+ : state.serving && (current || state.currentHidden)
273
+ ? "Current account"
274
+ : !state.automatic && !unknown && (!current || current.id === state.chosenId)
275
+ ? "Chosen account"
276
+ : current
277
+ ? "Current account"
278
+ : "Account selection";
279
+ const usageNearLimit = nearLimit(current?.usage);
280
+ const tone: "ok" | "warn" | "bad" =
281
+ current?.status === "needs_relogin" || current?.status === "error"
282
+ ? "bad"
283
+ : status || state.waiting || usageNearLimit
284
+ ? "warn"
285
+ : "ok";
286
+ const usage: SubscriptionAccountMenuUsage = !current
287
+ ? {
288
+ message: state.currentHidden
289
+ ? "Account usage hidden"
290
+ : unknown
291
+ ? "Usage unavailable"
292
+ : "Choose an account to see usage",
293
+ }
294
+ : state.usageCheck === "checking"
295
+ ? { checking: true }
296
+ : state.usageCheck === "failed"
297
+ ? { unavailable: true }
298
+ : subscriptionAccountUsage(current.usage);
299
+ const accounts: SubscriptionAccountMenuRow[] = state.accounts.map((account) => ({
300
+ id: account.id,
301
+ label: accountLabel(account),
302
+ plan: account.plan,
303
+ status: accountStatus(account),
304
+ detail: [
305
+ accountLabel(account),
306
+ account.email,
307
+ account.plan,
308
+ accountSource(account),
309
+ accountStatus(account),
310
+ ]
311
+ .filter(Boolean)
312
+ .join(" · "),
313
+ personal: account.source === "personal",
314
+ disabled: !account.choosable,
315
+ // The current account's usage is already shown above the list.
316
+ usageSummary:
317
+ account.id === current?.id || state.usageCheck === "checking"
318
+ ? undefined
319
+ : subscriptionAccountUsage(account.usage).summary,
320
+ }));
321
+ return {
322
+ ariaLabel: [
323
+ `${name} account`,
324
+ displayName,
325
+ state.waiting ? "Limit reached" : (status ?? (usageNearLimit ? "Near its limit" : null)),
326
+ ]
327
+ .filter(Boolean)
328
+ .join(" · "),
329
+ selectionLabel,
330
+ displayName,
331
+ identity: current?.email && current.email !== displayName ? current.email : null,
332
+ metadata: current
333
+ ? [current.plan, accountSource(current), status].filter(Boolean).join(" · ") ||
334
+ `${name} subscription`
335
+ : `${name} subscription`,
336
+ tone,
337
+ usage,
338
+ accounts,
339
+ selectedId: state.automatic || unknown ? null : state.chosenId,
340
+ automaticSelected: state.automatic && !unknown,
341
+ actionLabel: state.waiting ? "Retry with" : "Use for next turn",
342
+ automaticLabel: "Automatic",
343
+ emptyMessage: state.unavailable ? "Accounts unavailable." : "No accounts available.",
344
+ error: state.mutationError ? `Couldn't switch: ${state.mutationError}` : null,
345
+ note:
346
+ state.appliedTo === "waiting_turn"
347
+ ? "Selection saved. Capacity is being rechecked."
348
+ : state.appliedTo === "next_turn"
349
+ ? "Selection saved for the next turn."
350
+ : state.waiting
351
+ ? "Changing accounts retries the waiting turn."
352
+ : "Applies next turn. The current turn keeps its account.",
353
+ };
354
+ }
@@ -31,6 +31,7 @@ import { workspacesByOrganization } from "../org";
31
31
  import { hasWorkspacePermission } from "../permissions";
32
32
  import { readChatDirectory, recordChatVisit } from "./chat-directory";
33
33
  import {
34
+ extendChatSwitcher,
34
35
  isSwitcherKey,
35
36
  openChatSwitcher,
36
37
  stepChatSwitcher,
@@ -575,6 +576,16 @@ export function ActionsProvider(props: { children: ReactNode }) {
575
576
  const current = switcherRef.current;
576
577
  if (current) setSwitcher({ ...current, index });
577
578
  }}
579
+ onDirectoryChange={() => {
580
+ const current = switcherRef.current;
581
+ if (!current) return;
582
+ const next = extendChatSwitcher(
583
+ current,
584
+ readChatDirectory(subjectId, workspaceId),
585
+ reachableRef.current,
586
+ );
587
+ if (next !== current) setSwitcher(next);
588
+ }}
578
589
  onPick={pickSwitcherChat}
579
590
  onDismiss={() => setSwitcher(null)}
580
591
  />