@oxyhq/services 27.1.3 → 29.0.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 (387) hide show
  1. package/LICENSE +202 -661
  2. package/NOTICE +16 -0
  3. package/README.md +67 -2
  4. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionModule.kt +178 -0
  5. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionProvider.kt +99 -0
  6. package/android/src/main/java/so/oxy/devicesession/OxyDeviceSessionStore.kt +115 -0
  7. package/expo-module.config.json +1 -0
  8. package/lib/commonjs/index.js +78 -16
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/ui/boot/runProviderColdBoot.js +7 -0
  11. package/lib/commonjs/ui/boot/runProviderColdBoot.js.map +1 -1
  12. package/lib/commonjs/ui/client.js +12 -7
  13. package/lib/commonjs/ui/client.js.map +1 -1
  14. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js +10 -27
  15. package/lib/commonjs/ui/components/OxyAccountDialogScreen.js.map +1 -1
  16. package/lib/commonjs/ui/components/OxyAuthChooser.js +132 -44
  17. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  18. package/lib/commonjs/ui/components/OxyProvider.js +4 -2
  19. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  20. package/lib/commonjs/ui/components/SurfaceScreen.js +31 -3
  21. package/lib/commonjs/ui/components/SurfaceScreen.js.map +1 -1
  22. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +209 -84
  23. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  24. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js +25 -7
  25. package/lib/commonjs/ui/components/authChooser/SignInEntryView.js.map +1 -1
  26. package/lib/commonjs/ui/components/authChooser/primitives.js +35 -22
  27. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  28. package/lib/commonjs/ui/components/authChooser/signInProgress.js +5 -0
  29. package/lib/commonjs/ui/components/authChooser/signInProgress.js.map +1 -1
  30. package/lib/commonjs/ui/components/authChooser/types.js +11 -0
  31. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  32. package/lib/commonjs/ui/context/OxyContext.js +257 -306
  33. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  34. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +27 -37
  35. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  36. package/lib/commonjs/ui/context/oxyContextHelpers.js +0 -29
  37. package/lib/commonjs/ui/context/oxyContextHelpers.js.map +1 -1
  38. package/lib/commonjs/ui/context/useOxyAccountGraph.js +6 -17
  39. package/lib/commonjs/ui/context/useOxyAccountGraph.js.map +1 -1
  40. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js +42 -0
  41. package/lib/commonjs/ui/hooks/accountDialogSnapshot.js.map +1 -0
  42. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +0 -19
  43. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  44. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js +57 -0
  45. package/lib/commonjs/ui/hooks/useDeviceSwitcher.js.map +1 -0
  46. package/lib/commonjs/ui/hooks/useI18n.js +22 -3
  47. package/lib/commonjs/ui/hooks/useI18n.js.map +1 -1
  48. package/lib/commonjs/ui/hooks/useSessionManagement.js +46 -153
  49. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  50. package/lib/commonjs/ui/index.js +12 -7
  51. package/lib/commonjs/ui/index.js.map +1 -1
  52. package/lib/commonjs/ui/navigation/routes.js +85 -73
  53. package/lib/commonjs/ui/navigation/routes.js.map +1 -1
  54. package/lib/commonjs/ui/navigation/surfaces.js +8 -7
  55. package/lib/commonjs/ui/navigation/surfaces.js.map +1 -1
  56. package/lib/commonjs/ui/runtime/createOxyRuntime.js +433 -0
  57. package/lib/commonjs/ui/runtime/createOxyRuntime.js.map +1 -0
  58. package/lib/commonjs/ui/runtime/index.js +63 -0
  59. package/lib/commonjs/ui/runtime/index.js.map +1 -0
  60. package/lib/commonjs/ui/runtime/runtimeContext.js +83 -0
  61. package/lib/commonjs/ui/runtime/runtimeContext.js.map +1 -0
  62. package/lib/commonjs/ui/runtime/types.js +6 -0
  63. package/lib/commonjs/ui/runtime/types.js.map +1 -0
  64. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js +57 -0
  65. package/lib/commonjs/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  66. package/lib/commonjs/ui/screens/AccountMembersScreen.js +3 -4
  67. package/lib/commonjs/ui/screens/AccountMembersScreen.js.map +1 -1
  68. package/lib/commonjs/ui/screens/CreateAccountScreen.js +3 -12
  69. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  70. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js +6 -5
  71. package/lib/commonjs/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  72. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js +4 -4
  73. package/lib/commonjs/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  74. package/lib/commonjs/ui/screens/trust/TrustRewardsScreen.js +1 -1
  75. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js +4 -2
  76. package/lib/commonjs/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  77. package/lib/commonjs/ui/server.js +1 -2
  78. package/lib/commonjs/ui/server.js.map +1 -1
  79. package/lib/commonjs/ui/session/authStore.js +45 -9
  80. package/lib/commonjs/ui/session/authStore.js.map +1 -1
  81. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js +306 -0
  82. package/lib/commonjs/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  83. package/lib/commonjs/ui/stores/authStore.js +154 -55
  84. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  85. package/lib/commonjs/ui/stores/followTargetStore.js +27 -0
  86. package/lib/commonjs/ui/stores/followTargetStore.js.map +1 -1
  87. package/lib/commonjs/ui/utils/avatarUtils.js +2 -36
  88. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  89. package/lib/module/index.js +20 -9
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/ui/boot/runProviderColdBoot.js +7 -0
  92. package/lib/module/ui/boot/runProviderColdBoot.js.map +1 -1
  93. package/lib/module/ui/client.js +1 -2
  94. package/lib/module/ui/client.js.map +1 -1
  95. package/lib/module/ui/components/OxyAccountDialogScreen.js +10 -27
  96. package/lib/module/ui/components/OxyAccountDialogScreen.js.map +1 -1
  97. package/lib/module/ui/components/OxyAuthChooser.js +134 -44
  98. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  99. package/lib/module/ui/components/OxyProvider.js +5 -3
  100. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  101. package/lib/module/ui/components/SurfaceScreen.js +33 -5
  102. package/lib/module/ui/components/SurfaceScreen.js.map +1 -1
  103. package/lib/module/ui/components/authChooser/AccountsMenuView.js +210 -85
  104. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  105. package/lib/module/ui/components/authChooser/SignInEntryView.js +25 -7
  106. package/lib/module/ui/components/authChooser/SignInEntryView.js.map +1 -1
  107. package/lib/module/ui/components/authChooser/primitives.js +35 -22
  108. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  109. package/lib/module/ui/components/authChooser/signInProgress.js +5 -0
  110. package/lib/module/ui/components/authChooser/signInProgress.js.map +1 -1
  111. package/lib/module/ui/components/authChooser/types.js +11 -0
  112. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  113. package/lib/module/ui/context/OxyContext.js +251 -303
  114. package/lib/module/ui/context/OxyContext.js.map +1 -1
  115. package/lib/module/ui/context/hooks/useAuthOperations.js +27 -37
  116. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  117. package/lib/module/ui/context/oxyContextHelpers.js +0 -28
  118. package/lib/module/ui/context/oxyContextHelpers.js.map +1 -1
  119. package/lib/module/ui/context/useOxyAccountGraph.js +6 -17
  120. package/lib/module/ui/context/useOxyAccountGraph.js.map +1 -1
  121. package/lib/module/ui/hooks/accountDialogSnapshot.js +38 -0
  122. package/lib/module/ui/hooks/accountDialogSnapshot.js.map +1 -0
  123. package/lib/module/ui/hooks/mutations/useAccountMutations.js +0 -19
  124. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  125. package/lib/module/ui/hooks/useDeviceSwitcher.js +53 -0
  126. package/lib/module/ui/hooks/useDeviceSwitcher.js.map +1 -0
  127. package/lib/module/ui/hooks/useI18n.js +24 -4
  128. package/lib/module/ui/hooks/useI18n.js.map +1 -1
  129. package/lib/module/ui/hooks/useSessionManagement.js +47 -155
  130. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  131. package/lib/module/ui/index.js +1 -2
  132. package/lib/module/ui/index.js.map +1 -1
  133. package/lib/module/ui/navigation/routes.js +84 -72
  134. package/lib/module/ui/navigation/routes.js.map +1 -1
  135. package/lib/module/ui/navigation/surfaces.js +6 -6
  136. package/lib/module/ui/navigation/surfaces.js.map +1 -1
  137. package/lib/module/ui/runtime/createOxyRuntime.js +430 -0
  138. package/lib/module/ui/runtime/createOxyRuntime.js.map +1 -0
  139. package/lib/module/ui/runtime/index.js +15 -0
  140. package/lib/module/ui/runtime/index.js.map +1 -0
  141. package/lib/module/ui/runtime/runtimeContext.js +75 -0
  142. package/lib/module/ui/runtime/runtimeContext.js.map +1 -0
  143. package/lib/module/ui/runtime/types.js +4 -0
  144. package/lib/module/ui/runtime/types.js.map +1 -0
  145. package/lib/module/ui/runtime/useRuntimeSnapshot.js +52 -0
  146. package/lib/module/ui/runtime/useRuntimeSnapshot.js.map +1 -0
  147. package/lib/module/ui/screens/AccountMembersScreen.js +4 -5
  148. package/lib/module/ui/screens/AccountMembersScreen.js.map +1 -1
  149. package/lib/module/ui/screens/CreateAccountScreen.js +4 -13
  150. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  151. package/lib/module/ui/screens/trust/TrustCenterScreen.js +6 -5
  152. package/lib/module/ui/screens/trust/TrustCenterScreen.js.map +1 -1
  153. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js +5 -5
  154. package/lib/module/ui/screens/trust/TrustLeaderboardScreen.js.map +1 -1
  155. package/lib/module/ui/screens/trust/TrustRewardsScreen.js +1 -1
  156. package/lib/module/ui/screens/trust/TrustRulesScreen.js +4 -2
  157. package/lib/module/ui/screens/trust/TrustRulesScreen.js.map +1 -1
  158. package/lib/module/ui/server.js +0 -1
  159. package/lib/module/ui/server.js.map +1 -1
  160. package/lib/module/ui/session/authStore.js +46 -11
  161. package/lib/module/ui/session/authStore.js.map +1 -1
  162. package/lib/module/ui/session/sharedDeviceCredentialStore.js +302 -0
  163. package/lib/module/ui/session/sharedDeviceCredentialStore.js.map +1 -0
  164. package/lib/module/ui/stores/authStore.js +154 -55
  165. package/lib/module/ui/stores/authStore.js.map +1 -1
  166. package/lib/module/ui/stores/followTargetStore.js +25 -0
  167. package/lib/module/ui/stores/followTargetStore.js.map +1 -1
  168. package/lib/module/ui/utils/avatarUtils.js +2 -35
  169. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  170. package/lib/typescript/commonjs/index.d.ts +6 -4
  171. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  172. package/lib/typescript/commonjs/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/ui/client.d.ts +1 -2
  174. package/lib/typescript/commonjs/ui/client.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  176. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/ui/components/OxyProvider.d.ts.map +1 -1
  178. package/lib/typescript/commonjs/ui/components/SurfaceScreen.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  180. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  181. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  182. package/lib/typescript/commonjs/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  183. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +18 -6
  184. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  185. package/lib/typescript/commonjs/ui/components/authChooser/signInProgress.d.ts.map +1 -1
  186. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +33 -1
  187. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  188. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +35 -4
  189. package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
  190. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +8 -12
  191. package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts +0 -2
  193. package/lib/typescript/commonjs/ui/context/oxyContextHelpers.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts +9 -1
  195. package/lib/typescript/commonjs/ui/context/oxyContextTypes.d.ts.map +1 -1
  196. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts +1 -3
  197. package/lib/typescript/commonjs/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  198. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  199. package/lib/typescript/commonjs/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  200. package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  202. package/lib/typescript/commonjs/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  203. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts +13 -0
  204. package/lib/typescript/commonjs/ui/hooks/useI18n.d.ts.map +1 -1
  205. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts +23 -18
  206. package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/ui/index.d.ts +1 -2
  208. package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/ui/navigation/routes.d.ts +15 -1
  210. package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/ui/navigation/surfaces.d.ts.map +1 -1
  212. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts +144 -0
  213. package/lib/typescript/commonjs/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  214. package/lib/typescript/commonjs/ui/runtime/index.d.ts +16 -0
  215. package/lib/typescript/commonjs/ui/runtime/index.d.ts.map +1 -0
  216. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts +43 -0
  217. package/lib/typescript/commonjs/ui/runtime/runtimeContext.d.ts.map +1 -0
  218. package/lib/typescript/commonjs/ui/runtime/types.d.ts +89 -0
  219. package/lib/typescript/commonjs/ui/runtime/types.d.ts.map +1 -0
  220. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  221. package/lib/typescript/commonjs/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  222. package/lib/typescript/commonjs/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  223. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  224. package/lib/typescript/commonjs/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  225. package/lib/typescript/commonjs/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  226. package/lib/typescript/commonjs/ui/server.d.ts +0 -1
  227. package/lib/typescript/commonjs/ui/server.d.ts.map +1 -1
  228. package/lib/typescript/commonjs/ui/session/authStore.d.ts +60 -9
  229. package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -1
  230. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  231. package/lib/typescript/commonjs/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  232. package/lib/typescript/commonjs/ui/stores/authStore.d.ts +49 -5
  233. package/lib/typescript/commonjs/ui/stores/authStore.d.ts.map +1 -1
  234. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts +8 -1
  235. package/lib/typescript/commonjs/ui/stores/followTargetStore.d.ts.map +1 -1
  236. package/lib/typescript/commonjs/ui/types/navigation.d.ts +26 -0
  237. package/lib/typescript/commonjs/ui/types/navigation.d.ts.map +1 -1
  238. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts +2 -10
  239. package/lib/typescript/commonjs/ui/utils/avatarUtils.d.ts.map +1 -1
  240. package/lib/typescript/module/index.d.ts +6 -4
  241. package/lib/typescript/module/index.d.ts.map +1 -1
  242. package/lib/typescript/module/ui/boot/runProviderColdBoot.d.ts.map +1 -1
  243. package/lib/typescript/module/ui/client.d.ts +1 -2
  244. package/lib/typescript/module/ui/client.d.ts.map +1 -1
  245. package/lib/typescript/module/ui/components/OxyAccountDialogScreen.d.ts.map +1 -1
  246. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  247. package/lib/typescript/module/ui/components/OxyProvider.d.ts.map +1 -1
  248. package/lib/typescript/module/ui/components/SurfaceScreen.d.ts.map +1 -1
  249. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts +11 -4
  250. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  251. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts +3 -1
  252. package/lib/typescript/module/ui/components/authChooser/SignInEntryView.d.ts.map +1 -1
  253. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +18 -6
  254. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  255. package/lib/typescript/module/ui/components/authChooser/signInProgress.d.ts.map +1 -1
  256. package/lib/typescript/module/ui/components/authChooser/types.d.ts +33 -1
  257. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  258. package/lib/typescript/module/ui/context/OxyContext.d.ts +35 -4
  259. package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
  260. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +8 -12
  261. package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  262. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts +0 -2
  263. package/lib/typescript/module/ui/context/oxyContextHelpers.d.ts.map +1 -1
  264. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts +9 -1
  265. package/lib/typescript/module/ui/context/oxyContextTypes.d.ts.map +1 -1
  266. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts +1 -3
  267. package/lib/typescript/module/ui/context/useOxyAccountGraph.d.ts.map +1 -1
  268. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts +15 -0
  269. package/lib/typescript/module/ui/hooks/accountDialogSnapshot.d.ts.map +1 -0
  270. package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  271. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts +49 -0
  272. package/lib/typescript/module/ui/hooks/useDeviceSwitcher.d.ts.map +1 -0
  273. package/lib/typescript/module/ui/hooks/useI18n.d.ts +13 -0
  274. package/lib/typescript/module/ui/hooks/useI18n.d.ts.map +1 -1
  275. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts +23 -18
  276. package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
  277. package/lib/typescript/module/ui/index.d.ts +1 -2
  278. package/lib/typescript/module/ui/index.d.ts.map +1 -1
  279. package/lib/typescript/module/ui/navigation/routes.d.ts +15 -1
  280. package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
  281. package/lib/typescript/module/ui/navigation/surfaces.d.ts.map +1 -1
  282. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts +144 -0
  283. package/lib/typescript/module/ui/runtime/createOxyRuntime.d.ts.map +1 -0
  284. package/lib/typescript/module/ui/runtime/index.d.ts +16 -0
  285. package/lib/typescript/module/ui/runtime/index.d.ts.map +1 -0
  286. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts +43 -0
  287. package/lib/typescript/module/ui/runtime/runtimeContext.d.ts.map +1 -0
  288. package/lib/typescript/module/ui/runtime/types.d.ts +89 -0
  289. package/lib/typescript/module/ui/runtime/types.d.ts.map +1 -0
  290. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts +23 -0
  291. package/lib/typescript/module/ui/runtime/useRuntimeSnapshot.d.ts.map +1 -0
  292. package/lib/typescript/module/ui/screens/AccountMembersScreen.d.ts.map +1 -1
  293. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  294. package/lib/typescript/module/ui/screens/trust/TrustLeaderboardScreen.d.ts.map +1 -1
  295. package/lib/typescript/module/ui/screens/trust/TrustRulesScreen.d.ts.map +1 -1
  296. package/lib/typescript/module/ui/server.d.ts +0 -1
  297. package/lib/typescript/module/ui/server.d.ts.map +1 -1
  298. package/lib/typescript/module/ui/session/authStore.d.ts +60 -9
  299. package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -1
  300. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts +32 -0
  301. package/lib/typescript/module/ui/session/sharedDeviceCredentialStore.d.ts.map +1 -0
  302. package/lib/typescript/module/ui/stores/authStore.d.ts +49 -5
  303. package/lib/typescript/module/ui/stores/authStore.d.ts.map +1 -1
  304. package/lib/typescript/module/ui/stores/followTargetStore.d.ts +8 -1
  305. package/lib/typescript/module/ui/stores/followTargetStore.d.ts.map +1 -1
  306. package/lib/typescript/module/ui/types/navigation.d.ts +26 -0
  307. package/lib/typescript/module/ui/types/navigation.d.ts.map +1 -1
  308. package/lib/typescript/module/ui/utils/avatarUtils.d.ts +2 -10
  309. package/lib/typescript/module/ui/utils/avatarUtils.d.ts.map +1 -1
  310. package/package.json +8 -8
  311. package/plugins/withSharedDeviceSessionProvider.js +102 -0
  312. package/plugins/withSharedDeviceSessionReader.js +57 -0
  313. package/src/__tests__/esmSourcePurity.test.ts +61 -0
  314. package/src/index.ts +36 -9
  315. package/src/ui/boot/runProviderColdBoot.ts +7 -0
  316. package/src/ui/client.ts +1 -2
  317. package/src/ui/components/OxyAccountDialogScreen.tsx +10 -29
  318. package/src/ui/components/OxyAuthChooser.tsx +149 -50
  319. package/src/ui/components/OxyProvider.tsx +7 -5
  320. package/src/ui/components/SurfaceScreen.tsx +28 -4
  321. package/src/ui/components/__tests__/followTargetButton.test.ts +39 -0
  322. package/src/ui/components/authChooser/AccountsMenuView.tsx +246 -96
  323. package/src/ui/components/authChooser/SignInEntryView.tsx +31 -8
  324. package/src/ui/components/authChooser/primitives.tsx +37 -23
  325. package/src/ui/components/authChooser/signInProgress.ts +4 -0
  326. package/src/ui/components/authChooser/types.ts +34 -1
  327. package/src/ui/context/OxyContext.tsx +269 -324
  328. package/src/ui/context/hooks/useAuthOperations.ts +32 -35
  329. package/src/ui/context/oxyContextHelpers.ts +0 -30
  330. package/src/ui/context/oxyContextTypes.ts +9 -1
  331. package/src/ui/context/useOxyAccountGraph.ts +5 -19
  332. package/src/ui/hooks/accountDialogSnapshot.ts +37 -0
  333. package/src/ui/hooks/mutations/useAccountMutations.ts +0 -19
  334. package/src/ui/hooks/queries/__tests__/userCache.test.ts +12 -1
  335. package/src/ui/hooks/useDeviceSwitcher.ts +110 -0
  336. package/src/ui/hooks/useI18n.ts +23 -3
  337. package/src/ui/hooks/useSessionManagement.ts +62 -211
  338. package/src/ui/index.ts +1 -2
  339. package/src/ui/navigation/routes.ts +85 -75
  340. package/src/ui/navigation/surfaces.ts +6 -9
  341. package/src/ui/runtime/createOxyRuntime.ts +623 -0
  342. package/src/ui/runtime/index.ts +34 -0
  343. package/src/ui/runtime/runtimeContext.tsx +111 -0
  344. package/src/ui/runtime/types.ts +110 -0
  345. package/src/ui/runtime/useRuntimeSnapshot.ts +52 -0
  346. package/src/ui/screens/AccountMembersScreen.tsx +6 -5
  347. package/src/ui/screens/CreateAccountScreen.tsx +3 -16
  348. package/src/ui/screens/trust/TrustCenterScreen.tsx +7 -7
  349. package/src/ui/screens/trust/TrustLeaderboardScreen.tsx +4 -5
  350. package/src/ui/screens/trust/TrustRewardsScreen.tsx +1 -1
  351. package/src/ui/screens/trust/TrustRulesScreen.tsx +3 -2
  352. package/src/ui/server.ts +0 -1
  353. package/src/ui/session/__tests__/authStore.test.ts +90 -0
  354. package/src/ui/session/__tests__/sharedDeviceCredentialStore.test.ts +289 -0
  355. package/src/ui/session/authStore.ts +78 -9
  356. package/src/ui/session/sharedDeviceCredentialStore.ts +318 -0
  357. package/src/ui/stores/authStore.ts +171 -43
  358. package/src/ui/stores/followTargetStore.ts +24 -1
  359. package/src/ui/types/navigation.ts +26 -0
  360. package/src/ui/utils/avatarUtils.ts +2 -37
  361. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +0 -64
  362. package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +0 -1
  363. package/lib/commonjs/ui/screens/trust/trustTier.js +0 -23
  364. package/lib/commonjs/ui/screens/trust/trustTier.js.map +0 -1
  365. package/lib/commonjs/ui/stores/accountStore.js +0 -221
  366. package/lib/commonjs/ui/stores/accountStore.js.map +0 -1
  367. package/lib/module/ui/hooks/useSwitchableAccounts.js +0 -60
  368. package/lib/module/ui/hooks/useSwitchableAccounts.js.map +0 -1
  369. package/lib/module/ui/screens/trust/trustTier.js +0 -19
  370. package/lib/module/ui/screens/trust/trustTier.js.map +0 -1
  371. package/lib/module/ui/stores/accountStore.js +0 -213
  372. package/lib/module/ui/stores/accountStore.js.map +0 -1
  373. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  374. package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  375. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts +0 -9
  376. package/lib/typescript/commonjs/ui/screens/trust/trustTier.d.ts.map +0 -1
  377. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts +0 -27
  378. package/lib/typescript/commonjs/ui/stores/accountStore.d.ts.map +0 -1
  379. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +0 -23
  380. package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +0 -1
  381. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts +0 -9
  382. package/lib/typescript/module/ui/screens/trust/trustTier.d.ts.map +0 -1
  383. package/lib/typescript/module/ui/stores/accountStore.d.ts +0 -27
  384. package/lib/typescript/module/ui/stores/accountStore.d.ts.map +0 -1
  385. package/src/ui/hooks/useSwitchableAccounts.ts +0 -75
  386. package/src/ui/screens/trust/trustTier.ts +0 -20
  387. package/src/ui/stores/accountStore.ts +0 -264
@@ -7,8 +7,11 @@
7
7
  * avatar (with a camera badge to change the photo), the greeting, the account
8
8
  * address, and a "Manage your Oxy account" pill.
9
9
  * - SWITCH ACCOUNT: a disclosure row whose corner animates from pill to card as
10
- * the other switchable accounts + "Add another account" + "Manage accounts on
11
- * this device" reveal beneath it.
10
+ * the device's people + "Add another account" + "Manage accounts on this
11
+ * device" reveal beneath it. The list is grouped BY PERSON (ADR 0002): an
12
+ * account sits under the human who can operate it, and the same organization
13
+ * reachable through two people is two rows under two names — the thing a list
14
+ * keyed by account id structurally cannot say.
12
15
  * - OXY STORAGE: cloud + used/total + a composition meter + Upgrade / Manage chips.
13
16
  * - MENU rows: Your data in Oxy · Oxy settings · Help & feedback · Sign out —
14
17
  * Bloom's grouped section, the same component every other SDK settings surface
@@ -41,13 +44,18 @@ import { PressableScale } from '@oxyhq/bloom/pressable-scale';
41
44
  import { CompositionBar, type CompositionCategory } from '@oxyhq/bloom/composition-bar';
42
45
  import { SettingsListGroup, SettingsListItem } from '@oxyhq/bloom/settings-list';
43
46
  import { Text } from '@oxyhq/bloom/typography';
44
- import type { AccountDialogSnapshot, SwitchableAccount } from '@oxyhq/core';
45
- import { getNormalizedUserHandle } from '@oxyhq/core';
47
+ import {
48
+ showsPrincipalHeaders,
49
+ type AccountDialogSnapshot,
50
+ type SwitcherContextRow,
51
+ type SwitcherPrincipalRow,
52
+ } from '@oxyhq/core';
46
53
  import AvatarCameraBadge from '../AvatarCameraBadge';
47
54
  import { HoverPressable } from './primitives';
48
55
  import { authChooserStyles as styles } from './styles';
49
56
  import {
50
57
  resolveAccentHex,
58
+ type AccountHeroModel,
51
59
  type AccountStorageModel,
52
60
  type AccountsMenuActions,
53
61
  type OxyAuthChooserHandlers,
@@ -84,6 +92,11 @@ const SWITCH_CARD_RADIUS_SETTLE = 0.2;
84
92
  const SWITCH_REVEAL_MS = 240;
85
93
  /** Diameter of an account row's avatar in the switch list (own markup). */
86
94
  const SWITCH_AVATAR_SIZE = 36;
95
+ /**
96
+ * The person's avatar above their block of accounts. Smaller than the account
97
+ * rows beneath it: the header labels the group, the rows are the targets.
98
+ */
99
+ const PRINCIPAL_AVATAR_SIZE = 24;
87
100
  /** Glyph inside a bordered affordance badge, sized to the avatar it stands in for. */
88
101
  const BADGE_GLYPH_SIZE = 19;
89
102
  /** The hero block's large current-account avatar. */
@@ -99,20 +112,6 @@ const FACEPILE_AVATAR_SIZE = 32;
99
112
  */
100
113
  const FACEPILE_OVERLAP = 4;
101
114
 
102
- /**
103
- * The hero's address line, shown under the "Hi, <name>!" greeting: the account's
104
- * canonical `@oxy.so` email when it has one, else its normalized `@handle`. Never
105
- * synthesizes a `username@oxy.so` address (the identity contract forbids it) — a
106
- * non-Oxy or missing email falls back to `getNormalizedUserHandle`.
107
- */
108
- function heroAddressLine(account: SwitchableAccount): string | null {
109
- if (account.email?.toLowerCase().endsWith('@oxy.so')) {
110
- return account.email;
111
- }
112
- const handle = getNormalizedUserHandle(account.user);
113
- return handle ? `@${handle}` : null;
114
- }
115
-
116
115
  /**
117
116
  * `bytes` → `"11.6 GB"`. Whole plan sizes read as whole numbers (`"17 GB"`, not
118
117
  * `"17.0 GB"`), and anything ≥ 100 GB drops the decimal entirely.
@@ -125,6 +124,10 @@ function formatStorageGb(bytes: number): string {
125
124
 
126
125
  interface AccountsMenuViewProps {
127
126
  snapshot: AccountDialogSnapshot;
127
+ /** The device's people, each with the accounts they may act as. */
128
+ principals: SwitcherPrincipalRow[];
129
+ /** The current account, rendered large. `null` before the session resolves. */
130
+ hero: AccountHeroModel | null;
128
131
  theme: Theme;
129
132
  t: Translate;
130
133
  handlers: OxyAuthChooserHandlers;
@@ -134,6 +137,8 @@ interface AccountsMenuViewProps {
134
137
 
135
138
  const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
136
139
  snapshot,
140
+ principals,
141
+ hero,
137
142
  theme,
138
143
  t,
139
144
  handlers,
@@ -188,7 +193,7 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
188
193
  listProgress.value = withTiming(next ? 1 : 0, { duration: SWITCH_REVEAL_MS });
189
194
  }, [expanded, listProgress]);
190
195
 
191
- if (snapshot.loading && snapshot.accounts.length === 0) {
196
+ if (snapshot.loading && principals.length === 0) {
192
197
  return (
193
198
  <View className="items-center justify-center gap-space-12 py-space-24">
194
199
  <MaterialCommunityIcons name="loading" size={24} color={theme.colors.textSecondary} />
@@ -199,24 +204,26 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
199
204
  );
200
205
  }
201
206
 
202
- const switchingDisabled = snapshot.switchingAccountId !== null;
203
- const current = snapshot.accounts.find((account) => account.isCurrent) ?? snapshot.accounts[0];
204
- const others = current
205
- ? snapshot.accounts.filter((account) => account.accountId !== current.accountId)
206
- : snapshot.accounts;
207
- const currentAccent = current
208
- ? resolveAccentHex(current.color, theme.colors.primary)
209
- : theme.colors.primary;
210
- const currentAddress = current ? heroAddressLine(current) : null;
211
- // The switch list repeats the CURRENT account (first, ringed) unlike the old
212
- // compact header it is not represented anywhere else once the hero moved out
213
- // of the card, and the list is where you switch back to it.
214
- const switchableAccounts = current ? [current, ...others] : others;
215
- const switchableAccountKey = switchableAccounts.map((account) => account.accountId).join(':');
216
- const facepile: AvatarGroupItem[] = switchableAccounts.map((account) => ({
217
- id: account.accountId,
218
- uri: account.avatarUrl ?? undefined,
219
- displayName: account.displayName,
207
+ // Activation and the two removals all mutate the same device, so any one of
208
+ // them in flight disables every row: a second press would race the first for
209
+ // which context the server ends up electing as active.
210
+ const busy =
211
+ snapshot.activatingContextId !== null ||
212
+ snapshot.removingContextId !== null ||
213
+ snapshot.removingPrincipalId !== null;
214
+ const currentAccent = hero?.accentHex ?? theme.colors.primary;
215
+ // Naming the person above each block earns its place only when the grouping
216
+ // carries information a second person, or somebody with more than one
217
+ // account. On a one-person, one-account device it would just repeat the hero.
218
+ const showPrincipalHeaders = showsPrincipalHeaders(principals);
219
+ const contextRows = principals.flatMap((principal) => principal.contexts);
220
+ // Remounts the measured wrapper when the rows change, so the collapse animates
221
+ // to the new natural height instead of the previous one.
222
+ const switchListKey = contextRows.map((context) => context.contextId).join(':');
223
+ const facepile: AvatarGroupItem[] = contextRows.map((context) => ({
224
+ id: context.contextId,
225
+ uri: context.avatarUrl ?? undefined,
226
+ displayName: context.displayName,
220
227
  }));
221
228
 
222
229
  // Used vs free is a two-part COMPOSITION (the parts always fill the bar), not
@@ -253,62 +260,47 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
253
260
  // strands one against the header row.
254
261
  const listItems = (
255
262
  <>
256
- {switchableAccounts.map((account, index) => {
257
- const accent = resolveAccentHex(account.color, theme.colors.primary);
258
- const isSwitching = snapshot.switchingAccountId === account.accountId;
259
- return (
260
- <Fragment key={account.accountId}>
261
- {index > 0 ? <View className="h-px bg-border opacity-30 ml-space-12" /> : null}
262
- <HoverPressable
263
- baseClassName={`flex-row items-center gap-space-12 px-space-12 py-[10px]${
264
- switchingDisabled ? ' opacity-60' : ''
265
- }`}
266
- hoverClassName="bg-fill-secondary"
267
- onPress={() => handlers.onSwitch(account.accountId)}
268
- disabled={switchingDisabled}
269
- accessibilityRole="button"
270
- accessibilityLabel={account.displayName}
271
- >
272
- <View>
273
- <Avatar
274
- source={account.avatarUrl ?? undefined}
275
- variant="thumb"
276
- name={account.displayName}
277
- size={SWITCH_AVATAR_SIZE}
278
- />
279
- {/* The accent ring is an OVERLAY: it costs the row no width, so
280
- every avatar in the list starts on the same content line. */}
281
- {account.isCurrent ? (
282
- <View
283
- style={[styles.currentAvatarRing, { borderColor: accent }]}
284
- pointerEvents="none"
285
- />
286
- ) : null}
287
- </View>
288
- <View className="flex-1 min-w-0">
289
- <Text className="text-body font-semibold text-text" numberOfLines={1}>
290
- {account.displayName}
291
- </Text>
292
- {account.email ? (
293
- <Text className="text-caption text-text-secondary" numberOfLines={1}>
294
- {account.email}
295
- </Text>
296
- ) : null}
297
- </View>
298
- {isSwitching ? (
299
- <MaterialCommunityIcons name="loading" size={20} color={accent} />
263
+ {principals.map((principal, principalIndex) => (
264
+ <Fragment key={principal.principalId}>
265
+ {principalIndex > 0 ? <View className="h-px bg-border opacity-30" /> : null}
266
+ {showPrincipalHeaders ? (
267
+ <PrincipalHeaderRow
268
+ principal={principal}
269
+ removing={snapshot.removingPrincipalId === principal.principalId}
270
+ disabled={busy}
271
+ onSignOut={() => handlers.onRemovePrincipal(principal.principalId)}
272
+ theme={theme}
273
+ t={t}
274
+ />
275
+ ) : null}
276
+ {principal.contexts.map((context, contextIndex) => (
277
+ <Fragment key={context.contextId}>
278
+ {contextIndex > 0 || showPrincipalHeaders ? (
279
+ <View className="h-px bg-border opacity-30 ml-space-12" />
300
280
  ) : null}
301
- </HoverPressable>
302
- </Fragment>
303
- );
304
- })}
281
+ <ContextRow
282
+ context={context}
283
+ principalName={principal.displayName}
284
+ accent={resolveAccentHex(context.color, theme.colors.primary)}
285
+ activating={snapshot.activatingContextId === context.contextId}
286
+ removing={snapshot.removingContextId === context.contextId}
287
+ disabled={busy}
288
+ onActivate={() => handlers.onActivate(context.contextId)}
289
+ onRemove={() => handlers.onRemoveContext(context.contextId)}
290
+ theme={theme}
291
+ t={t}
292
+ />
293
+ </Fragment>
294
+ ))}
295
+ </Fragment>
296
+ ))}
305
297
 
306
298
  <View className="h-px bg-border opacity-30 ml-space-12" />
307
299
  <AccountAffordanceRow
308
300
  icon="plus"
309
301
  label={t('accountMenu.addAnother')}
310
302
  onPress={handlers.onAdd}
311
- disabled={switchingDisabled}
303
+ disabled={busy}
312
304
  theme={theme}
313
305
  />
314
306
  <View className="h-px bg-border opacity-30 ml-space-12" />
@@ -316,7 +308,7 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
316
308
  icon="account-multiple-outline"
317
309
  label={t('accountSwitcher.manageOnDevice')}
318
310
  onPress={handlers.onManage}
319
- disabled={switchingDisabled}
311
+ disabled={busy}
320
312
  theme={theme}
321
313
  />
322
314
  </>
@@ -332,7 +324,7 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
332
324
  large pressable avatar (with a camera badge to change the photo), then
333
325
  the greeting, then the account address under it, then the manage action. */}
334
326
  <View style={styles.hero}>
335
- {current ? (
327
+ {hero ? (
336
328
  <>
337
329
  <PressableScale
338
330
  onPress={handlers.onEditAvatar}
@@ -341,8 +333,8 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
341
333
  style={styles.heroAvatarPressable}
342
334
  >
343
335
  <Avatar
344
- source={current.avatarUrl ?? undefined}
345
- name={current.displayName}
336
+ source={hero.avatarUrl ?? undefined}
337
+ name={hero.displayName}
346
338
  size={HERO_AVATAR_SIZE}
347
339
  />
348
340
  <View
@@ -353,14 +345,14 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
353
345
  </PressableScale>
354
346
  <View style={styles.heroNameBlock}>
355
347
  <Text style={[styles.heroGreeting, { color: theme.colors.text }]} numberOfLines={2}>
356
- {t('accountMenu.greeting', { name: current.displayName })}
348
+ {t('accountMenu.greeting', { name: hero.displayName })}
357
349
  </Text>
358
- {currentAddress ? (
350
+ {hero.addressLine ? (
359
351
  <Text
360
352
  style={[styles.heroAddress, { color: theme.colors.textSecondary }]}
361
353
  numberOfLines={1}
362
354
  >
363
- {currentAddress}
355
+ {hero.addressLine}
364
356
  </Text>
365
357
  ) : null}
366
358
  </View>
@@ -387,11 +379,11 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
387
379
  <View className="bg-fill">
388
380
  <HoverPressable
389
381
  baseClassName={`flex-row items-center gap-space-12 px-space-12 py-[10px] min-h-[44px]${
390
- switchingDisabled ? ' opacity-60' : ''
382
+ busy ? ' opacity-60' : ''
391
383
  }`}
392
384
  hoverClassName="bg-fill-secondary"
393
385
  onPress={toggleExpanded}
394
- disabled={switchingDisabled}
386
+ disabled={busy}
395
387
  accessibilityRole="button"
396
388
  // The ARIA prop, not `accessibilityState`: react-native-web 0.21
397
389
  // forwards only this one, and RN maps it to
@@ -432,7 +424,7 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
432
424
  pointerEvents={expanded ? 'auto' : 'none'}
433
425
  >
434
426
  <View
435
- key={switchableAccountKey}
427
+ key={switchListKey}
436
428
  style={styles.collapseMeasure}
437
429
  onLayout={(event) => setListContentHeight(event.nativeEvent.layout.height)}
438
430
  >
@@ -520,6 +512,164 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
520
512
  );
521
513
  };
522
514
 
515
+ /**
516
+ * The person a block of accounts hangs off, with the one action that removes
517
+ * THEM rather than one of their accounts.
518
+ *
519
+ * The distinction is the reason this row exists: dropping Alice's route to an
520
+ * organization is not the same as dropping Alice, and on a shared device the
521
+ * second must not touch Nate's route to the same organization. Two different
522
+ * server calls, two different rows, never one control doing both.
523
+ */
524
+ const PrincipalHeaderRow: React.FC<{
525
+ principal: SwitcherPrincipalRow;
526
+ removing: boolean;
527
+ disabled: boolean;
528
+ onSignOut: () => void;
529
+ theme: Theme;
530
+ t: Translate;
531
+ }> = ({ principal, removing, disabled, onSignOut, theme, t }) => {
532
+ const signOutLabel = t('accountSwitcher.principal.signOut', { name: principal.displayName });
533
+ return (
534
+ <View className="flex-row items-center gap-space-12 px-space-12 pt-space-12 pb-space-8">
535
+ <Avatar
536
+ source={principal.avatarUrl ?? undefined}
537
+ variant="thumb"
538
+ name={principal.displayName}
539
+ size={PRINCIPAL_AVATAR_SIZE}
540
+ />
541
+ <View className="flex-1 min-w-0">
542
+ <Text className="text-caption font-semibold text-text" numberOfLines={1}>
543
+ {principal.displayName}
544
+ </Text>
545
+ {principal.handle ? (
546
+ <Text className="text-caption text-text-tertiary" numberOfLines={1}>
547
+ {`@${principal.handle}`}
548
+ </Text>
549
+ ) : null}
550
+ </View>
551
+ <HoverPressable
552
+ baseClassName={`rounded-radius-max px-space-8 py-[4px]${disabled ? ' opacity-60' : ''}`}
553
+ hoverClassName="bg-fill-secondary"
554
+ onPress={onSignOut}
555
+ disabled={disabled}
556
+ accessibilityRole="button"
557
+ accessibilityLabel={signOutLabel}
558
+ >
559
+ {removing ? (
560
+ <MaterialCommunityIcons name="loading" size={16} color={theme.colors.textSecondary} />
561
+ ) : (
562
+ <Text className="text-caption text-text-secondary">{t('accountMenu.signOut')}</Text>
563
+ )}
564
+ </HoverPressable>
565
+ </View>
566
+ );
567
+ };
568
+
569
+ /**
570
+ * One `principal acting as account` row.
571
+ *
572
+ * Pressing it activates the PAIR — a `contextId`, not an account id, because on
573
+ * this device the account below may also be reachable through somebody else.
574
+ * A row the server marks unavailable is rendered rather than hidden, so a
575
+ * membership going away is something the person can see instead of a row that
576
+ * silently stopped existing; it is disabled, because activating it is a request
577
+ * the server answers with a 403 and then heals the row away.
578
+ */
579
+ const ContextRow: React.FC<{
580
+ context: SwitcherContextRow;
581
+ principalName: string;
582
+ /**
583
+ * This ROW's own accent, resolved from `context.color` by the caller. Never
584
+ * the signed-in account's: two people on one device would then be drawn in
585
+ * whichever of them happens to be active.
586
+ */
587
+ accent: string;
588
+ activating: boolean;
589
+ removing: boolean;
590
+ disabled: boolean;
591
+ onActivate: () => void;
592
+ onRemove: () => void;
593
+ theme: Theme;
594
+ t: Translate;
595
+ }> = ({
596
+ context,
597
+ principalName,
598
+ accent,
599
+ activating,
600
+ removing,
601
+ disabled,
602
+ onActivate,
603
+ onRemove,
604
+ theme,
605
+ t,
606
+ }) => {
607
+ const unavailable = !context.canActivate;
608
+ const rowDisabled = disabled || unavailable;
609
+ const removeLabel = t('accountSwitcher.context.remove', {
610
+ account: context.displayName,
611
+ person: principalName,
612
+ });
613
+ return (
614
+ <View className="flex-row items-center">
615
+ <HoverPressable
616
+ baseClassName={`flex-1 min-w-0 flex-row items-center gap-space-12 pl-space-12 py-[10px]${
617
+ rowDisabled ? ' opacity-60' : ''
618
+ }`}
619
+ hoverClassName="bg-fill-secondary"
620
+ onPress={onActivate}
621
+ disabled={rowDisabled}
622
+ accessibilityRole="button"
623
+ accessibilityState={{ selected: context.isActive, disabled: rowDisabled }}
624
+ accessibilityLabel={context.displayName}
625
+ >
626
+ <View>
627
+ <Avatar
628
+ source={context.avatarUrl ?? undefined}
629
+ variant="thumb"
630
+ name={context.displayName}
631
+ size={SWITCH_AVATAR_SIZE}
632
+ />
633
+ {/* The accent ring is an OVERLAY: it costs the row no width, so every
634
+ avatar in the list starts on the same content line. */}
635
+ {context.isActive ? (
636
+ <View style={[styles.currentAvatarRing, { borderColor: accent }]} pointerEvents="none" />
637
+ ) : null}
638
+ </View>
639
+ <View className="flex-1 min-w-0">
640
+ <Text className="text-body font-semibold text-text" numberOfLines={1}>
641
+ {context.displayName}
642
+ </Text>
643
+ <Text className="text-caption text-text-secondary" numberOfLines={1}>
644
+ {unavailable
645
+ ? t('accountSwitcher.context.unavailable')
646
+ : context.handle
647
+ ? `@${context.handle}`
648
+ : ''}
649
+ </Text>
650
+ </View>
651
+ {activating ? (
652
+ <MaterialCommunityIcons name="loading" size={20} color={accent} />
653
+ ) : null}
654
+ </HoverPressable>
655
+ <HoverPressable
656
+ baseClassName={`px-space-12 py-[10px]${disabled ? ' opacity-60' : ''}`}
657
+ hoverClassName="bg-fill-secondary"
658
+ onPress={onRemove}
659
+ disabled={disabled}
660
+ accessibilityRole="button"
661
+ accessibilityLabel={removeLabel}
662
+ >
663
+ <MaterialCommunityIcons
664
+ name={removing ? 'loading' : 'close'}
665
+ size={18}
666
+ color={theme.colors.textTertiary}
667
+ />
668
+ </HoverPressable>
669
+ </View>
670
+ );
671
+ };
672
+
523
673
  /**
524
674
  * An account-list AFFORDANCE row ("Add another account", "Manage accounts on
525
675
  * this device") — a bordered circle sized to the avatar it stands in for, so its
@@ -19,7 +19,11 @@
19
19
  import type React from 'react';
20
20
  import { View } from 'react-native';
21
21
  import { Button } from '@oxyhq/bloom/button';
22
- import type { AccountDialogSnapshot } from '@oxyhq/core';
22
+ import {
23
+ showsPrincipalHeaders,
24
+ type AccountDialogSnapshot,
25
+ type SwitcherPrincipalRow,
26
+ } from '@oxyhq/core';
23
27
  import TroubleDisclosure from './TroubleDisclosure';
24
28
  import { AccountRow, Dividerish, SubtleLink } from './primitives';
25
29
  import { authChooserStyles as styles } from './styles';
@@ -33,6 +37,8 @@ import type {
33
37
 
34
38
  interface SignInEntryViewProps {
35
39
  snapshot: AccountDialogSnapshot;
40
+ /** The device's people, each with the accounts they may act as. */
41
+ principals: SwitcherPrincipalRow[];
36
42
  theme: Theme;
37
43
  t: Translate;
38
44
  handlers: OxyAuthChooserHandlers;
@@ -43,6 +49,7 @@ interface SignInEntryViewProps {
43
49
 
44
50
  const SignInEntryView: React.FC<SignInEntryViewProps> = ({
45
51
  snapshot,
52
+ principals,
46
53
  theme,
47
54
  t,
48
55
  handlers,
@@ -76,18 +83,34 @@ const SignInEntryView: React.FC<SignInEntryViewProps> = ({
76
83
  },
77
84
  ];
78
85
 
86
+ // Whose route this is only needs naming when the list holds more than one
87
+ // person, or somebody with more than one account — the same rule the account
88
+ // menu's group headers follow, so the two surfaces agree on when the actor is
89
+ // worth stating.
90
+ const namesTheOperator = showsPrincipalHeaders(principals);
91
+ const rows = principals.flatMap((principal) =>
92
+ principal.contexts.map((context) => ({
93
+ context,
94
+ operatedBy:
95
+ namesTheOperator && context.isDelegated
96
+ ? t('accountSwitcher.context.operatedBy', { name: principal.displayName })
97
+ : null,
98
+ })),
99
+ );
100
+
79
101
  return (
80
102
  <View style={styles.signInBlock}>
81
- {snapshot.accounts.length > 0 ? (
103
+ {rows.length > 0 ? (
82
104
  <View style={styles.rows}>
83
- {snapshot.accounts.map((account) => (
105
+ {rows.map(({ context, operatedBy }) => (
84
106
  <AccountRow
85
- key={account.accountId}
86
- account={account}
107
+ key={context.contextId}
108
+ context={context}
109
+ operatedBy={operatedBy}
87
110
  theme={theme}
88
- switching={snapshot.switchingAccountId === account.accountId}
89
- disabled={snapshot.switchingAccountId !== null}
90
- onPress={() => handlers.onSwitch(account.accountId)}
111
+ activating={snapshot.activatingContextId === context.contextId}
112
+ disabled={snapshot.activatingContextId !== null}
113
+ onPress={() => handlers.onActivate(context.contextId)}
91
114
  />
92
115
  ))}
93
116
  <Dividerish theme={theme} label={t('signin.or')} />
@@ -9,9 +9,9 @@ import { useState } from 'react';
9
9
  import { Pressable, View } from 'react-native-css/components';
10
10
  import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
11
11
  import { Avatar } from '@oxyhq/bloom/avatar';
12
- import { Text } from '@oxyhq/bloom/typography';
13
12
  import { BloomColorScope } from '@oxyhq/bloom/theme';
14
- import type { SwitchableAccount } from '@oxyhq/core';
13
+ import { Text } from '@oxyhq/bloom/typography';
14
+ import type { SwitcherContextRow } from '@oxyhq/core';
15
15
  import { authChooserStyles as styles } from './styles';
16
16
  import { resolveAccentHex, toPreset, type Theme } from './types';
17
17
 
@@ -83,57 +83,71 @@ export const Dividerish: React.FC<{ theme: Theme; label: string }> = ({ theme, l
83
83
  );
84
84
 
85
85
  /**
86
- * An account row on the sign-in entry — "continue as one of these" rather than a
87
- * choice of authentication METHOD, which is why it stays above the primary CTA
88
- * instead of behind the trouble disclosure.
86
+ * A `principal acting as account` row on the sign-in entry — "continue as one of
87
+ * these" rather than a choice of authentication METHOD, which is why it stays
88
+ * above the primary CTA instead of behind the trouble disclosure.
89
+ *
90
+ * Pressing it activates the PAIR. The secondary line is the account's `@handle`:
91
+ * the device directory carries a handle and no email, deliberately — it is the
92
+ * minimum that renders a row for every person on the device, and this surface is
93
+ * reached while signed out, where fetching anyone's profile is not an option.
94
+ *
95
+ * The accent comes from the row's OWN account (`context.color`, straight off the
96
+ * directory), not from the theme and not from whoever is signed in. This surface
97
+ * is where a device holding two people is most visible, and it is reached while
98
+ * signed out — so there is no "current user" whose colour could stand in.
89
99
  */
90
100
  export const AccountRow: React.FC<{
91
- account: SwitchableAccount;
101
+ context: SwitcherContextRow;
102
+ /** The person this account is reached through, when that is not obvious. */
103
+ operatedBy: string | null;
92
104
  theme: Theme;
93
- switching: boolean;
105
+ activating: boolean;
94
106
  disabled: boolean;
95
107
  onPress: () => void;
96
- }> = ({ account, theme, switching, disabled, onPress }) => {
97
- const accent = resolveAccentHex(account.color, theme.colors.primary);
108
+ }> = ({ context, operatedBy, theme, activating, disabled, onPress }) => {
109
+ const accent = resolveAccentHex(context.color, theme.colors.primary);
110
+ const rowDisabled = disabled || !context.canActivate;
111
+ const secondary = operatedBy ?? (context.handle ? `@${context.handle}` : null);
98
112
 
99
113
  return (
100
- <BloomColorScope colorPreset={toPreset(account.color)} asChild>
114
+ <BloomColorScope colorPreset={toPreset(context.color)} asChild>
101
115
  <Pressable
102
116
  style={[
103
117
  styles.accountRow,
104
118
  {
105
- borderColor: account.isCurrent ? accent : theme.colors.border,
119
+ borderColor: context.isActive ? accent : theme.colors.border,
106
120
  backgroundColor: theme.colors.card,
107
121
  },
108
- disabled && !switching ? styles.rowDisabled : null,
122
+ rowDisabled && !activating ? styles.rowDisabled : null,
109
123
  ]}
110
124
  onPress={onPress}
111
- disabled={disabled}
125
+ disabled={rowDisabled}
112
126
  accessibilityRole="button"
113
- accessibilityState={{ selected: account.isCurrent, disabled }}
114
- accessibilityLabel={account.displayName}
127
+ accessibilityState={{ selected: context.isActive, disabled: rowDisabled }}
128
+ accessibilityLabel={context.displayName}
115
129
  >
116
- <View style={[styles.avatarRing, { borderColor: account.isCurrent ? accent : 'transparent' }]}>
130
+ <View style={[styles.avatarRing, { borderColor: context.isActive ? accent : 'transparent' }]}>
117
131
  <Avatar
118
- source={account.avatarUrl ?? undefined}
132
+ source={context.avatarUrl ?? undefined}
119
133
  variant="thumb"
120
- name={account.displayName}
134
+ name={context.displayName}
121
135
  size={ROW_AVATAR_SIZE}
122
136
  />
123
137
  </View>
124
138
  <View style={styles.rowMeta}>
125
139
  <Text style={[styles.rowName, { color: theme.colors.text }]} numberOfLines={1}>
126
- {account.displayName}
140
+ {context.displayName}
127
141
  </Text>
128
- {account.email ? (
142
+ {secondary ? (
129
143
  <Text style={[styles.rowHandle, { color: theme.colors.textSecondary }]} numberOfLines={1}>
130
- {account.email}
144
+ {secondary}
131
145
  </Text>
132
146
  ) : null}
133
147
  </View>
134
- {switching ? (
148
+ {activating ? (
135
149
  <MaterialCommunityIcons name="loading" size={20} color={accent} />
136
- ) : account.isCurrent ? (
150
+ ) : context.isActive ? (
137
151
  <MaterialCommunityIcons name="check-circle" size={20} color={accent} />
138
152
  ) : (
139
153
  <MaterialCommunityIcons name="chevron-right" size={20} color={theme.colors.textSecondary} />
@@ -50,5 +50,9 @@ export function signInProgressLabel(
50
50
  return t('accountSwitcher.progress.confirming');
51
51
  case 'identity-confirmed':
52
52
  return t('accountSwitcher.progress.confirmed');
53
+ default: {
54
+ const _exhaustive: never = progress;
55
+ return _exhaustive;
56
+ }
53
57
  }
54
58
  }