@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
@@ -37,11 +37,12 @@ import type React from 'react';
37
37
  import { useCallback, useMemo, useState, useSyncExternalStore } from 'react';
38
38
  import { Linking, Platform } from 'react-native';
39
39
  import { toast } from '@oxyhq/bloom/toast';
40
+ import { surfaces } from '@oxyhq/bloom/surfaces';
40
41
  import { useTheme } from '@oxyhq/bloom/theme';
41
- import type { AccountDialogSnapshot } from '@oxyhq/core';
42
- import { isOxyRpOrigin } from '@oxyhq/core';
42
+ import { getNormalizedUserHandle, isOxyRpOrigin, type User } from '@oxyhq/core';
43
43
  import { useQueryClient } from '@tanstack/react-query';
44
44
  import { useOxy } from '../context/OxyContext';
45
+ import { useDeviceSwitcher } from '../hooks/useDeviceSwitcher';
45
46
  import { useI18n } from '../hooks/useI18n';
46
47
  import {
47
48
  getAccountDialogConsumerHooks,
@@ -54,13 +55,16 @@ import AccountsMenuView from './authChooser/AccountsMenuView';
54
55
  import SignInEntryView from './authChooser/SignInEntryView';
55
56
  import SignInRequestView from './authChooser/SignInRequestView';
56
57
  import SignUpView from './authChooser/SignUpView';
57
- import type {
58
- AccountStorageModel,
59
- AccountsMenuActions,
60
- OxyAuthChooserHandlers,
61
- PasskeyMode,
62
- SignInAlternatives,
58
+ import {
59
+ resolveAccentHex,
60
+ type AccountHeroModel,
61
+ type AccountStorageModel,
62
+ type AccountsMenuActions,
63
+ type OxyAuthChooserHandlers,
64
+ type PasskeyMode,
65
+ type SignInAlternatives,
63
66
  } from './authChooser/types';
67
+ import { EMPTY_ACCOUNT_DIALOG_SNAPSHOT } from '../hooks/accountDialogSnapshot';
64
68
 
65
69
  /** Commons' own identity-creation deep link (mirrors the `approve`/`attest`/`card` intents). */
66
70
  const COMMONS_CREATE_IDENTITY_URL = 'oxycommons://create-identity';
@@ -96,16 +100,20 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
96
100
  const {
97
101
  accountDialogController: controller,
98
102
  showBottomSheet,
99
- refreshAccounts,
100
103
  signInWithPasskey,
101
104
  registerWithPasskey,
102
105
  oxyServices,
103
106
  logout,
104
107
  openAvatarPicker,
108
+ user,
105
109
  } = useOxy();
106
110
  const theme = useTheme();
107
111
  const { t } = useI18n();
108
112
  const queryClient = useQueryClient();
113
+ // The device's people and what each may act as, resolved for rendering — the
114
+ // SAME hook (and the same one builder) the auth.oxy.so chooser renders from,
115
+ // so the two switchers cannot drift.
116
+ const { principals } = useDeviceSwitcher();
109
117
 
110
118
  // A credential minted for `oxy.so` can only be ASSERTED there (or a loopback
111
119
  // dev host) — a hard WebAuthn RP-ID boundary the browser enforces, not
@@ -217,7 +225,7 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
217
225
  [controller, autoStartSignIn],
218
226
  );
219
227
  const getSnapshot = useCallback(
220
- () => (controller ? controller.getSnapshot() : EMPTY_SNAPSHOT),
228
+ () => (controller ? controller.getSnapshot() : EMPTY_ACCOUNT_DIALOG_SNAPSHOT),
221
229
  [controller],
222
230
  );
223
231
  const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
@@ -228,31 +236,97 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
228
236
  () => null,
229
237
  );
230
238
 
231
- const handleSwitch = useCallback(
232
- async (accountId: string) => {
239
+ const handleActivate = useCallback(
240
+ async (contextId: string) => {
233
241
  if (!controller) return;
234
- if (controller.getSnapshot().switchingAccountId) return;
235
- if (accountId === controller.getSnapshot().activeAccountId) {
242
+ if (controller.getSnapshot().activatingContextId) return;
243
+ // Already live: activating it again bumps nothing server-side, so treat
244
+ // the press as "yes, this one" and close.
245
+ if (contextId === controller.getSnapshot().activeContext?.contextId) {
236
246
  onComplete?.();
237
247
  return;
238
248
  }
239
249
  try {
240
- await controller.switchTo(accountId);
241
- // `switchTo` records failures on the controller snapshot. Read that
242
- // state after the operation so the same path works with both the real
243
- // controller and framework test doubles that return void.
250
+ await controller.activateContext(contextId);
251
+ // Failures are recorded on the controller snapshot rather than thrown,
252
+ // so the same path works with the real controller and with test doubles
253
+ // that resolve void.
244
254
  if (controller.getSnapshot().error) {
245
- toast.error(t('accountSwitcher.toasts.switchFailed'));
255
+ toast.error(t('accountSwitcher.toasts.activateFailed'));
246
256
  return;
247
257
  }
248
- void refreshAccounts();
258
+ // The subject changed, so every account-scoped query is now about
259
+ // somebody else. The runtime resets its own caches between the bearer
260
+ // commit and the notify; this drops the Query cache the same way the
261
+ // account switch did.
249
262
  queryClient.invalidateQueries();
250
263
  onComplete?.();
251
264
  } catch {
252
- toast.error(t('accountSwitcher.toasts.switchFailed'));
265
+ toast.error(t('accountSwitcher.toasts.activateFailed'));
266
+ }
267
+ },
268
+ [controller, onComplete, queryClient, t],
269
+ );
270
+
271
+ /**
272
+ * Remove ONE `principal → account` pair.
273
+ *
274
+ * Confirmed, and the confirmation says whose route is going: on a shared
275
+ * device the same organization is reachable through somebody else, and their
276
+ * access is untouched. That is the fact a single "remove account" control
277
+ * would get wrong.
278
+ */
279
+ const handleRemoveContext = useCallback(
280
+ async (contextId: string) => {
281
+ if (!controller) return;
282
+ const group = principals.find((principal) =>
283
+ principal.contexts.some((context) => context.contextId === contextId),
284
+ );
285
+ const context = group?.contexts.find((row) => row.contextId === contextId);
286
+ if (!group || !context) return;
287
+ const confirmed = await surfaces.confirm({
288
+ title: t('accountSwitcher.confirms.removeContextTitle'),
289
+ message: t('accountSwitcher.confirms.removeContext', {
290
+ person: group.displayName,
291
+ account: context.displayName,
292
+ }),
293
+ confirmLabel: t('common.remove'),
294
+ cancelLabel: t('common.cancel'),
295
+ destructive: true,
296
+ });
297
+ if (!confirmed) return;
298
+ const removed = await controller.signOutContext(contextId);
299
+ if (!removed) {
300
+ toast.error(t('accountSwitcher.toasts.contextRemoveFailed'));
301
+ return;
302
+ }
303
+ toast.success(t('accountSwitcher.toasts.contextRemoved', { account: context.displayName }));
304
+ },
305
+ [controller, principals, t],
306
+ );
307
+
308
+ /** Remove ONE PERSON and every account they reach here, and nobody else's. */
309
+ const handleRemovePrincipal = useCallback(
310
+ async (principalId: string) => {
311
+ if (!controller) return;
312
+ const group = principals.find((principal) => principal.principalId === principalId);
313
+ if (!group) return;
314
+ const confirmed = await surfaces.confirm({
315
+ title: t('accountSwitcher.confirms.removePrincipalTitle'),
316
+ message: t('accountSwitcher.confirms.removePrincipal', { name: group.displayName }),
317
+ confirmLabel: t('common.actions.signOut'),
318
+ cancelLabel: t('common.cancel'),
319
+ destructive: true,
320
+ });
321
+ if (!confirmed) return;
322
+ const removed = await controller.signOutPrincipal(principalId);
323
+ if (!removed) {
324
+ toast.error(t('accountSwitcher.toasts.principalRemoveFailed'));
325
+ return;
253
326
  }
327
+ toast.success(t('accountSwitcher.toasts.principalRemoved', { name: group.displayName }));
254
328
  },
255
- [controller, onComplete, refreshAccounts, queryClient, t],
329
+ [controller, principals, t],
256
330
  );
257
331
 
258
332
  const handleManage = useCallback(() => {
@@ -278,8 +352,14 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
278
352
 
279
353
  const handlers = useMemo<OxyAuthChooserHandlers>(
280
354
  () => ({
281
- onSwitch: (accountId) => {
282
- void handleSwitch(accountId);
355
+ onActivate: (contextId) => {
356
+ void handleActivate(contextId);
357
+ },
358
+ onRemoveContext: (contextId) => {
359
+ void handleRemoveContext(contextId);
360
+ },
361
+ onRemovePrincipal: (principalId) => {
362
+ void handleRemovePrincipal(principalId);
283
363
  },
284
364
  onAdd: handleAdd,
285
365
  onManage: handleManage,
@@ -287,7 +367,21 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
287
367
  // uploads / clears the picked photo — the single shared avatar write path.
288
368
  onEditAvatar: () => openAvatarPicker(),
289
369
  }),
290
- [handleSwitch, handleAdd, handleManage, openAvatarPicker],
370
+ [handleActivate, handleRemoveContext, handleRemovePrincipal, handleAdd, handleManage, openAvatarPicker],
371
+ );
372
+
373
+ /**
374
+ * The hero block — the account this client is signed in as, from its FULL
375
+ * profile rather than from a directory row.
376
+ *
377
+ * The directory carries the minimum that renders a row for every person on
378
+ * the device; this is the one account we legitimately hold everything for, so
379
+ * the hero keeps its real EMAIL, which the rows below do not pretend to have.
380
+ * The accent is not in that category — every row is drawn in its own account's
381
+ * (issue #961), this one included, from the same field.
382
+ */
383
+ const hero = useMemo<AccountHeroModel | null>(() => (user ? buildHero(user, theme.colors.primary, oxyServices) : null),
384
+ [user, theme.colors.primary, oxyServices],
291
385
  );
292
386
 
293
387
  // Everything that is NOT the surface's one primary action. Wired once here;
@@ -373,6 +467,8 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
373
467
  return (
374
468
  <AccountsMenuView
375
469
  snapshot={snapshot}
470
+ principals={principals}
471
+ hero={hero}
376
472
  theme={theme}
377
473
  t={t}
378
474
  handlers={handlers}
@@ -417,6 +513,7 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
417
513
  return (
418
514
  <SignInEntryView
419
515
  snapshot={snapshot}
516
+ principals={principals}
420
517
  theme={theme}
421
518
  t={t}
422
519
  handlers={handlers}
@@ -426,30 +523,32 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
426
523
  );
427
524
  };
428
525
 
429
- // ---------------------------------------------------------------------------
430
- // Empty snapshot (no-provider loading state)
431
- // ---------------------------------------------------------------------------
432
-
433
- const EMPTY_SNAPSHOT: AccountDialogSnapshot = {
434
- view: 'accounts',
435
- accounts: [],
436
- activeAccountId: null,
437
- loading: false,
438
- error: null,
439
- switchingAccountId: null,
440
- signIn: {
441
- phase: 'idle',
442
- authorizeCode: null,
443
- qrPayload: null,
444
- expiresAt: null,
445
- error: null,
446
- route: null,
447
- routeFailed: false,
448
- pushSentAt: null,
449
- openedAt: null,
450
- progress: 'idle',
451
- },
452
- commonsAvailability: 'unknown',
453
- };
526
+ /**
527
+ * The hero model for the signed-in account.
528
+ *
529
+ * The address line is the account's canonical `@oxy.so` email when it has one,
530
+ * else its normalized `@handle`. It never synthesizes a `username@oxy.so`
531
+ * address — the identity contract forbids it, and a non-Oxy or missing email
532
+ * falls through to the handle.
533
+ */
534
+ function buildHero(
535
+ user: User,
536
+ fallbackAccent: string,
537
+ oxyServices: ReturnType<typeof useOxy>['oxyServices'],
538
+ ): AccountHeroModel {
539
+ const handle = getNormalizedUserHandle(user);
540
+ const displayName = user.name?.displayName?.trim() || handle || '';
541
+ const addressLine = user.email?.toLowerCase().endsWith('@oxy.so')
542
+ ? user.email
543
+ : handle
544
+ ? `@${handle}`
545
+ : null;
546
+ return {
547
+ displayName,
548
+ addressLine,
549
+ avatarUrl: user.avatar ? oxyServices.getFileDownloadUrl(user.avatar, 'thumb') : undefined,
550
+ accentHex: resolveAccentHex(user.color ?? null, fallbackAccent),
551
+ };
552
+ }
454
553
 
455
554
  export default OxyAuthChooser;
@@ -3,7 +3,7 @@ import { AppState, Platform, StyleSheet, View } from 'react-native';
3
3
  import { GestureHandlerRootView } from 'react-native-gesture-handler';
4
4
  import { SafeAreaProvider } from 'react-native-safe-area-context';
5
5
  import type { OxyProviderProps } from '../types/navigation';
6
- import { OxyContextProvider, type OxyContextProviderProps } from '../context/OxyContext';
6
+ import { OxyRuntimeProvider, type OxyRuntimeProviderProps } from '../context/OxyContext';
7
7
  import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
8
8
  import { BloomDialogProvider } from '@oxyhq/bloom/dialog';
9
9
  import { SurfaceProvider } from '@oxyhq/bloom/surfaces';
@@ -97,6 +97,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
97
97
  queryClient: providedQueryClient,
98
98
  requireAuth = 'off',
99
99
  backgroundSession = false,
100
+ deviceCredentialStorage = 'persistent',
100
101
  }) => {
101
102
 
102
103
  // Dynamic KeyboardProvider for native. Uses variable indirection
@@ -284,8 +285,8 @@ const OxyProvider: FC<OxyProviderProps> = ({
284
285
  const coreContent = (
285
286
  <QueryClientProvider client={queryClient}>
286
287
  <BloomDialogProvider>
287
- <OxyContextProvider
288
- oxyServices={oxyServices as OxyContextProviderProps['oxyServices']}
288
+ <OxyRuntimeProvider
289
+ oxyServices={oxyServices as OxyRuntimeProviderProps['oxyServices']}
289
290
  baseURL={baseURL}
290
291
  authWebUrl={authWebUrl}
291
292
  authRedirectUri={authRedirectUri}
@@ -295,7 +296,8 @@ const OxyProvider: FC<OxyProviderProps> = ({
295
296
  sessionMode={sessionMode}
296
297
  webAuthMode={webAuthMode}
297
298
  backgroundSession={backgroundSession}
298
- onAuthStateChange={onAuthStateChange as OxyContextProviderProps['onAuthStateChange']}
299
+ deviceCredentialStorage={deviceCredentialStorage}
300
+ onAuthStateChange={onAuthStateChange as OxyRuntimeProviderProps['onAuthStateChange']}
299
301
  >
300
302
  <SurfaceProvider>
301
303
  {requireAuth === 'off' ? (
@@ -305,7 +307,7 @@ const OxyProvider: FC<OxyProviderProps> = ({
305
307
  )}
306
308
  </SurfaceProvider>
307
309
  <ToastOutlet />
308
- </OxyContextProvider>
310
+ </OxyRuntimeProvider>
309
311
  </BloomDialogProvider>
310
312
  </QueryClientProvider>
311
313
  );
@@ -1,5 +1,5 @@
1
- import React, { useCallback, useEffect, useMemo, useState, type ErrorInfo } from 'react';
2
- import { StyleSheet, Text, View, useWindowDimensions } from 'react-native';
1
+ import React, { Suspense, useCallback, useEffect, useMemo, useState, type ErrorInfo } from 'react';
2
+ import { ActivityIndicator, StyleSheet, Text, View, useWindowDimensions } from 'react-native';
3
3
  import { useStore } from 'zustand';
4
4
  import { useTheme } from '@oxyhq/bloom/theme';
5
5
  import {
@@ -25,7 +25,7 @@ import {
25
25
  } from '../hooks/useSurfaceHeader';
26
26
  import type { BaseScreenProps } from '../types/navigation';
27
27
 
28
- /** Error boundary catching screen render failures (e.g. a lazy `require()` throw). */
28
+ /** Error boundary catching screen render failures, so one bad screen cannot blank the surface. */
29
29
  interface ScreenErrorBoundaryState {
30
30
  error: Error | null;
31
31
  }
@@ -69,12 +69,34 @@ class ScreenErrorBoundary extends React.Component<
69
69
  }
70
70
  }
71
71
 
72
+ /** The body fill shared by the two states that replace a screen: crashed, and not loaded yet. */
72
73
  const errorStyles = StyleSheet.create({
73
74
  container: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 24 },
74
75
  title: { fontSize: 16, fontWeight: '600', marginBottom: 8 },
75
76
  message: { fontSize: 13, textAlign: 'center' },
76
77
  });
77
78
 
79
+ /**
80
+ * Shown while a route's screen module is still loading. Screens are registered
81
+ * as `lazy(() => import(...))` (see `../navigation/routes`), so the very first
82
+ * presentation of a surface can land here for a frame; the surface chrome is
83
+ * already on screen by then, so this fills only the body.
84
+ *
85
+ * Deliberately RN's `ActivityIndicator` rather than `@oxyhq/bloom/loading`,
86
+ * matching `authChooser/requestSurfaces`: Bloom still declares
87
+ * `sideEffects: false`, so its `Loading` can still tree-shake to `undefined` in
88
+ * a rolldown-vite production bundle. Removing that risk from the first frame of
89
+ * every route is not worth a themed spinner.
90
+ */
91
+ function ScreenPending() {
92
+ const theme = useTheme();
93
+ return (
94
+ <View style={errorStyles.container}>
95
+ <ActivityIndicator size="large" color={theme.colors.primary} />
96
+ </View>
97
+ );
98
+ }
99
+
78
100
  export interface SurfaceScreenProps {
79
101
  /** This surface's route history (the NAV-WITHIN axis). */
80
102
  navStack: SurfaceNavStack;
@@ -251,7 +273,9 @@ function SurfaceScreen({
251
273
  return (
252
274
  <SurfaceHeaderContext.Provider value={headerContext}>
253
275
  <ScreenErrorBoundary screenName={top.route}>
254
- <ScreenComponent {...screenProps} />
276
+ <Suspense fallback={<ScreenPending />}>
277
+ <ScreenComponent {...screenProps} />
278
+ </Suspense>
255
279
  </ScreenErrorBoundary>
256
280
  </SurfaceHeaderContext.Provider>
257
281
  );
@@ -19,6 +19,8 @@ import {
19
19
  } from '../FollowTargetButton';
20
20
  import {
21
21
  isCompleteFollowStatus,
22
+ followRecordToStatus,
23
+ followRecordsToStatusMap,
22
24
  isFollowedGlobally,
23
25
  UNKNOWN_FOLLOW_STATUS,
24
26
  withApplicationMode,
@@ -165,6 +167,43 @@ describe('withApplicationMode', () => {
165
167
  ).toBe(false);
166
168
  expect(isCompleteFollowStatus(UNKNOWN_FOLLOW_STATUS)).toBe(true);
167
169
  });
170
+
171
+ it('converts a list row into a complete follow status', () => {
172
+ const status = followRecordToStatus({
173
+ relationshipId: 'rel-1',
174
+ target: { id: 'tgt-1', uri: 'https://example.test/t/1', kind: 'oxy.topic' },
175
+ globalState: 'active',
176
+ applicationMode: 'inherit',
177
+ effectiveState: 'following',
178
+ originApplicationId: 'app-a',
179
+ createdAt: '2026-01-01T00:00:00.000Z',
180
+ });
181
+ expect(status).toEqual({
182
+ relationshipId: 'rel-1',
183
+ globalState: 'active',
184
+ applicationMode: 'inherit',
185
+ effectiveState: 'following',
186
+ });
187
+ expect(isCompleteFollowStatus(status)).toBe(true);
188
+ expect(followRecordsToStatusMap([
189
+ {
190
+ relationshipId: 'rel-1',
191
+ target: { id: 'tgt-1', uri: 'https://example.test/t/1', kind: 'oxy.topic' },
192
+ globalState: 'active',
193
+ applicationMode: 'disabled',
194
+ effectiveState: 'not_following',
195
+ originApplicationId: null,
196
+ createdAt: '2026-01-01T00:00:00.000Z',
197
+ },
198
+ ])).toEqual({
199
+ 'tgt-1': {
200
+ relationshipId: 'rel-1',
201
+ globalState: 'active',
202
+ applicationMode: 'disabled',
203
+ effectiveState: 'not_following',
204
+ },
205
+ });
206
+ });
168
207
  });
169
208
 
170
209
  describe('what onChange reports', () => {