@oxyhq/services 26.0.0 → 26.0.3

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 (79) hide show
  1. package/lib/commonjs/index.js.map +1 -1
  2. package/lib/commonjs/ui/components/AvatarCameraBadge.js +2 -2
  3. package/lib/commonjs/ui/components/AvatarCameraBadge.js.map +1 -1
  4. package/lib/commonjs/ui/components/OxyAuthChooser.js +20 -16
  5. package/lib/commonjs/ui/components/OxyAuthChooser.js.map +1 -1
  6. package/lib/commonjs/ui/components/ProfileButton.js +19 -25
  7. package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
  8. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js +88 -73
  9. package/lib/commonjs/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  10. package/lib/commonjs/ui/components/authChooser/primitives.js +9 -9
  11. package/lib/commonjs/ui/components/authChooser/primitives.js.map +1 -1
  12. package/lib/commonjs/ui/components/authChooser/types.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/useAuth.js +4 -2
  14. package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
  15. package/lib/commonjs/ui/navigation/accountDialogManager.js +2 -0
  16. package/lib/commonjs/ui/navigation/accountDialogManager.js.map +1 -1
  17. package/lib/commonjs/ui/screens/CreateAccountScreen.js +11 -1
  18. package/lib/commonjs/ui/screens/CreateAccountScreen.js.map +1 -1
  19. package/lib/module/index.js.map +1 -1
  20. package/lib/module/ui/components/AvatarCameraBadge.js +1 -1
  21. package/lib/module/ui/components/AvatarCameraBadge.js.map +1 -1
  22. package/lib/module/ui/components/OxyAuthChooser.js +20 -16
  23. package/lib/module/ui/components/OxyAuthChooser.js.map +1 -1
  24. package/lib/module/ui/components/ProfileButton.js +10 -16
  25. package/lib/module/ui/components/ProfileButton.js.map +1 -1
  26. package/lib/module/ui/components/authChooser/AccountsMenuView.js +69 -54
  27. package/lib/module/ui/components/authChooser/AccountsMenuView.js.map +1 -1
  28. package/lib/module/ui/components/authChooser/primitives.js +1 -1
  29. package/lib/module/ui/components/authChooser/primitives.js.map +1 -1
  30. package/lib/module/ui/components/authChooser/types.js.map +1 -1
  31. package/lib/module/ui/hooks/useAuth.js +4 -2
  32. package/lib/module/ui/hooks/useAuth.js.map +1 -1
  33. package/lib/module/ui/navigation/accountDialogManager.js +2 -0
  34. package/lib/module/ui/navigation/accountDialogManager.js.map +1 -1
  35. package/lib/module/ui/screens/CreateAccountScreen.js +11 -1
  36. package/lib/module/ui/screens/CreateAccountScreen.js.map +1 -1
  37. package/lib/typescript/commonjs/index.d.ts +1 -0
  38. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/ui/components/AvatarCameraBadge.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/ui/components/OxyAuthChooser.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +3 -0
  42. package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
  43. package/lib/typescript/commonjs/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts +1 -1
  45. package/lib/typescript/commonjs/ui/components/authChooser/primitives.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts +6 -0
  47. package/lib/typescript/commonjs/ui/components/authChooser/types.d.ts.map +1 -1
  48. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +6 -4
  49. package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts +8 -0
  51. package/lib/typescript/commonjs/ui/navigation/accountDialogManager.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  53. package/lib/typescript/module/index.d.ts +1 -0
  54. package/lib/typescript/module/index.d.ts.map +1 -1
  55. package/lib/typescript/module/ui/components/AvatarCameraBadge.d.ts.map +1 -1
  56. package/lib/typescript/module/ui/components/OxyAuthChooser.d.ts.map +1 -1
  57. package/lib/typescript/module/ui/components/ProfileButton.d.ts +3 -0
  58. package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
  59. package/lib/typescript/module/ui/components/authChooser/AccountsMenuView.d.ts.map +1 -1
  60. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts +1 -1
  61. package/lib/typescript/module/ui/components/authChooser/primitives.d.ts.map +1 -1
  62. package/lib/typescript/module/ui/components/authChooser/types.d.ts +6 -0
  63. package/lib/typescript/module/ui/components/authChooser/types.d.ts.map +1 -1
  64. package/lib/typescript/module/ui/hooks/useAuth.d.ts +6 -4
  65. package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
  66. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts +8 -0
  67. package/lib/typescript/module/ui/navigation/accountDialogManager.d.ts.map +1 -1
  68. package/lib/typescript/module/ui/screens/CreateAccountScreen.d.ts.map +1 -1
  69. package/package.json +16 -7
  70. package/src/index.ts +1 -0
  71. package/src/ui/components/AvatarCameraBadge.tsx +2 -1
  72. package/src/ui/components/OxyAuthChooser.tsx +17 -16
  73. package/src/ui/components/ProfileButton.tsx +10 -18
  74. package/src/ui/components/authChooser/AccountsMenuView.tsx +66 -52
  75. package/src/ui/components/authChooser/primitives.tsx +1 -1
  76. package/src/ui/components/authChooser/types.ts +6 -0
  77. package/src/ui/hooks/useAuth.ts +8 -4
  78. package/src/ui/navigation/accountDialogManager.ts +9 -0
  79. package/src/ui/screens/CreateAccountScreen.tsx +12 -2
@@ -1 +1 @@
1
- {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/primitives.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAQ,MAAM,cAAc,CAAC;AAK/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAA8B,KAAK,KAAK,EAAE,MAAM,YAAS,CAAC;AAKjE,KAAK,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,EAAE,WAAW,CAAC,GAAG;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAcxD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAWA,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAMhE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAChC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAgDA,CAAC"}
1
+ {"version":3,"file":"primitives.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/primitives.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,EAAE,SAAS,EAAQ,MAAM,6BAA6B,CAAC;AAK9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,OAAO,EAA8B,KAAK,KAAK,EAAE,MAAM,YAAS,CAAC;AAKjE,KAAK,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,SAAS,CAAC,EAAE,WAAW,CAAC,GAAG;IACrF,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAcxD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,KAAK,CAAC;IACb,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAWA,CAAC;AAEF,wDAAwD;AACxD,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAMhE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC;IAChC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAgDA,CAAC"}
@@ -56,6 +56,12 @@ export interface AccountsMenuActions {
56
56
  onPrivacy: () => void;
57
57
  onTerms: () => void;
58
58
  onSignOut: () => void;
59
+ customItems: readonly {
60
+ key: string;
61
+ label: string;
62
+ icon?: string;
63
+ onPress: () => void;
64
+ }[];
59
65
  }
60
66
  /**
61
67
  * Where a WebAuthn ceremony can run from the current origin.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsC,KAAK,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC1G,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAqB,CAAC;AAEnD,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,uEAAuE;IACvE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,uEAAuE;IACvE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,uCAAuC;IACvC,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG/E;AAED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,GAAG,SAAS,CAKvE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/ui/components/authChooser/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsC,KAAK,YAAY,EAAE,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC1G,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAqB,CAAC;AAEnD,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,WAAW,sBAAsB;IACrC,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,yFAAyF;AACzF,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,eAAe,EAAE,MAAM,IAAI,CAAC;IAC5B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,EAAE,SAAS;QACpB,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,IAAI,CAAC;KACrB,EAAE,CAAC;CACL;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,gBAAgB,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,uEAAuE;IACvE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,uEAAuE;IACvE,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,uCAAuC;IACvC,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG/E;AAED,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,GAAG,SAAS,CAKvE"}
@@ -66,8 +66,8 @@ export interface AuthState {
66
66
  export interface AuthActions {
67
67
  /**
68
68
  * Sign in
69
- * - Web: Redirects to auth.oxy.so (no public key needed)
70
- * - Native: Uses cryptographic identity from keychain
69
+ * - Web + native: opens the in-app Oxy account dialog (`openAccountDialog('signin')`).
70
+ * Never navigates to auth.oxy.so that origin is third-party OAuth only.
71
71
  *
72
72
  * @param publicKey - Native: identity public key. Ignored on web.
73
73
  */
@@ -88,6 +88,8 @@ export interface AuthActions {
88
88
  export interface UseAuthReturn extends AuthState, AuthActions {
89
89
  /** Access to full OxyServices instance for advanced usage */
90
90
  oxyServices: ReturnType<typeof useOxy>['oxyServices'];
91
+ /** Switch the device session to another account on this device */
92
+ switchToAccount: ReturnType<typeof useOxy>['switchToAccount'];
91
93
  /** Open a bottom sheet screen (e.g. 'ManageAccount', 'FileManagement') */
92
94
  showBottomSheet: ReturnType<typeof useOxy>['showBottomSheet'];
93
95
  /** Open the avatar picker bottom sheet */
@@ -99,8 +101,8 @@ export interface UseAuthReturn extends AuthState, AuthActions {
99
101
  * Features:
100
102
  * - Zero config: Just wrap with OxyProvider and use
101
103
  * - Cross-platform: Same API on native and web
102
- * - Auto session restore: Web apps silently restore via device-secret mint, then
103
- * silent OAuth against the IdP hub; native apps use the shared keychain
104
+ * - Auto session restore: device-first cold boot (`deviceId` + `deviceSecret` mint);
105
+ * never auto-redirects to the IdP
104
106
  * - Type-safe: Full TypeScript support
105
107
  */
106
108
  export declare function useAuth(): UseAuthReturn;
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,0BAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAE1B;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAE7B;;;;;;;;;OASG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,0EAA0E;IAC1E,eAAe,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,0CAA0C;IAC1C,gBAAgB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAmFvC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,0BAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,gBAAgB,EAAE,OAAO,CAAC;IAE1B;;;;OAIG;IACH,mBAAmB,EAAE,OAAO,CAAC;IAE7B;;;;;;;;;OASG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;OAMG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,kEAAkE;IAClE,eAAe,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,0EAA0E;IAC1E,eAAe,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,0CAA0C;IAC1C,gBAAgB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjE;AAED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,IAAI,aAAa,CAqFvC"}
@@ -10,6 +10,13 @@
10
10
  * view) from a non-React call site.
11
11
  */
12
12
  import type { AccountDialogView } from '@oxyhq/core';
13
+ /** An app-owned action rendered inside the shared account menu. */
14
+ export interface AccountDialogMenuItem {
15
+ key: string;
16
+ label: string;
17
+ icon?: string;
18
+ onPress: () => void;
19
+ }
13
20
  /**
14
21
  * Live open/close handles registered by the mounted provider. `open` presents
15
22
  * the `AccountDialog` surface (and points its view); `close` dismisses it. Both
@@ -28,6 +35,7 @@ export interface AccountDialogConsumerHooks {
28
35
  onNavigateManage?: () => void;
29
36
  onAddAccount?: () => void;
30
37
  onNavigateProfile?: () => void;
38
+ menuItems?: readonly AccountDialogMenuItem[];
31
39
  }
32
40
  export declare function registerAccountDialogControls(next: AccountDialogControls): () => void;
33
41
  /** Register app-local manage/add/profile handlers for the account dialog. */
@@ -1 +1 @@
1
- {"version":3,"file":"accountDialogManager.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/accountDialogManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAMD,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAOrF;AAED,6EAA6E;AAC7E,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,0BAA0B,GAAG,IAAI,GACtC,MAAM,IAAI,CAOZ;AAED,wBAAgB,6BAA6B,IAAI,0BAA0B,GAAG,IAAI,CAEjF;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,sFAAsF;AACtF,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIpE;AAED,sDAAsD;AACtD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAKzF"}
1
+ {"version":3,"file":"accountDialogManager.d.ts","sourceRoot":"","sources":["../../../../../src/ui/navigation/accountDialogManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,mEAAmE;AACnE,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IACzC,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC9C;AAMD,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,IAAI,CAOrF;AAED,6EAA6E;AAC7E,wBAAgB,kCAAkC,CAChD,IAAI,EAAE,0BAA0B,GAAG,IAAI,GACtC,MAAM,IAAI,CAOZ;AAED,wBAAgB,6BAA6B,IAAI,0BAA0B,GAAG,IAAI,CAEjF;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAEhE;AAED,oDAAoD;AACpD,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED,sFAAsF;AACtF,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIpE;AAED,sDAAsD;AACtD,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,MAAM,IAAI,CAKzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AAiF3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+SlD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"CreateAccountScreen.d.ts","sourceRoot":"","sources":["../../../../../src/ui/screens/CreateAccountScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAqB,CAAC;AA2F3D;;;;;GAKG;AACH,QAAA,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA+SlD,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "26.0.0",
3
+ "version": "26.0.3",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -138,8 +138,8 @@
138
138
  }
139
139
  },
140
140
  "dependencies": {
141
- "@oxyhq/contracts": "0.20.0",
142
- "@oxyhq/core": "^17.0.0",
141
+ "@oxyhq/contracts": "0.21.0",
142
+ "@oxyhq/core": "^17.0.2",
143
143
  "@simplewebauthn/browser": "^13.3.0",
144
144
  "color": "^4.2.3"
145
145
  },
@@ -148,7 +148,7 @@
148
148
  "@commitlint/cli": "^21.2.1",
149
149
  "@commitlint/config-conventional": "^21.2.0",
150
150
  "@expo/vector-icons": "^15.0.3",
151
- "@oxyhq/core": "17.0.0",
151
+ "@oxyhq/core": "17.0.2",
152
152
  "@react-native-async-storage/async-storage": "^2.0.0",
153
153
  "@react-native-community/netinfo": "^11.4.1",
154
154
  "@react-native/babel-preset": "^0.86.0",
@@ -169,12 +169,13 @@
169
169
  "expo-image": "~57.0.1",
170
170
  "expo-linear-gradient": "~57.0.1",
171
171
  "expo-notifications": "~57.0.5",
172
+ "expo-web-browser": "~57.0.1",
172
173
  "husky": "^4.3.8",
173
174
  "jest": "~29.7.0",
174
175
  "lint-staged": "^15.2.4",
175
176
  "nativewind": "5.0.0-preview.3",
176
177
  "react-native-builder-bob": "^0.43.0",
177
- "react-native-css": "^3.0.0",
178
+ "react-native-css": "^3.0.6",
178
179
  "react-native-gesture-handler": "~2.32.0",
179
180
  "react-native-keyboard-controller": "1.21.13",
180
181
  "react-native-qrcode-svg": "^6.3.0",
@@ -190,7 +191,7 @@
190
191
  "peerDependencies": {
191
192
  "@expo/vector-icons": "^15.0.3",
192
193
  "@oxyhq/bloom": ">=0.59.0",
193
- "@oxyhq/core": "^17.0.0",
194
+ "@oxyhq/core": "^17.0.2",
194
195
  "@react-native-async-storage/async-storage": "^2.0.0",
195
196
  "@react-native-community/netinfo": ">=11.4.1",
196
197
  "@tanstack/query-async-storage-persister": "^5.101",
@@ -208,9 +209,11 @@
208
209
  "expo-image-manipulator": ">=56.0.0",
209
210
  "expo-image-picker": ">=56.0.0",
210
211
  "expo-linear-gradient": ">=56.0.0",
211
- "expo-notifications": ">=56.0.0",
212
212
  "expo-modules-core": "*",
213
+ "expo-notifications": ">=56.0.0",
214
+ "expo-web-browser": ">=56.0.0",
213
215
  "nativewind": ">=5.0.0",
216
+ "react-native-css": "^3.0.6",
214
217
  "react": ">=18.0.0",
215
218
  "react-native": ">=0.85.0",
216
219
  "react-native-gesture-handler": ">=2.31.1",
@@ -226,6 +229,9 @@
226
229
  "nativewind": {
227
230
  "optional": true
228
231
  },
232
+ "react-native-css": {
233
+ "optional": true
234
+ },
229
235
  "expo-modules-core": {
230
236
  "optional": true
231
237
  },
@@ -264,6 +270,9 @@
264
270
  },
265
271
  "expo-image-picker": {
266
272
  "optional": true
273
+ },
274
+ "expo-web-browser": {
275
+ "optional": true
267
276
  }
268
277
  },
269
278
  "react-native-builder-bob": {
package/src/index.ts CHANGED
@@ -275,6 +275,7 @@ export { LogoText } from './ui/components/logo/LogoText';
275
275
  // `useOxy().openAccountDialog`.
276
276
  export { default as ProfileButton } from './ui/components/ProfileButton';
277
277
  export type { ProfileButtonProps } from './ui/components/ProfileButton';
278
+ export type { AccountDialogMenuItem } from './ui/navigation/accountDialogManager';
278
279
 
279
280
  // The account switcher/sign-in/sign-up chooser WITHOUT dialog chrome — the
280
281
  // surface stack presents `OxyAccountDialogScreen` (which renders this) into
@@ -1,5 +1,6 @@
1
1
  import type React from 'react';
2
- import { View, type ViewStyle } from 'react-native';
2
+ import type { ViewStyle } from 'react-native';
3
+ import { View } from 'react-native-css/components';
3
4
  import Ionicons from '@expo/vector-icons/Ionicons';
4
5
  import { useTheme } from '@oxyhq/bloom/theme';
5
6
 
@@ -220,25 +220,19 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
220
220
  const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
221
221
  const { view } = snapshot;
222
222
 
223
- const [switching, setSwitching] = useState(false);
224
-
225
223
  const handleSwitch = useCallback(
226
224
  async (accountId: string) => {
227
- if (!controller || switching) return;
228
- if (accountId === snapshot.activeAccountId) {
225
+ if (!controller) return;
226
+ if (controller.getSnapshot().switchingAccountId) return;
227
+ if (accountId === controller.getSnapshot().activeAccountId) {
229
228
  onComplete?.();
230
229
  return;
231
230
  }
232
- setSwitching(true);
233
231
  try {
234
232
  await controller.switchTo(accountId);
235
- // `switchTo` never throws — it records a failure on the controller's
236
- // snapshot. Read it back at the point the switch settles and surface it
237
- // as a toast (event-driven, at the failure site — NOT an inline banner
238
- // and NOT a snapshot-reaction). On success reload the app's account
239
- // graph and drop cached account-scoped data so the new active identity
240
- // re-fetches — the same side effects the context's own
241
- // `switchToAccount` performs.
233
+ // `switchTo` records failures on the controller snapshot. Read that
234
+ // state after the operation so the same path works with both the real
235
+ // controller and framework test doubles that return void.
242
236
  if (controller.getSnapshot().error) {
243
237
  toast.error(t('accountSwitcher.toasts.switchFailed'));
244
238
  return;
@@ -246,11 +240,11 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
246
240
  void refreshAccounts();
247
241
  queryClient.invalidateQueries();
248
242
  onComplete?.();
249
- } finally {
250
- setSwitching(false);
243
+ } catch {
244
+ // `switchTo` is documented as non-throwing; guard against regressions.
251
245
  }
252
246
  },
253
- [controller, switching, snapshot.activeAccountId, onComplete, refreshAccounts, queryClient, t],
247
+ [controller, onComplete, refreshAccounts, queryClient, t],
254
248
  );
255
249
 
256
250
  const handleManage = useCallback(() => {
@@ -317,7 +311,7 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
317
311
  // Real storage usage for the account menu's "Oxy storage" block. Disabled
318
312
  // (no fetch) until a private-API session exists, so it is inert on the
319
313
  // sign-in/request/sign-up views; when present the block shows live used/total.
320
- const storageQuery = useAccountStorageUsage();
314
+ const storageQuery = useAccountStorageUsage({ enabled: view === 'accounts' });
321
315
  const storage = useMemo<AccountStorageModel | null>(
322
316
  () =>
323
317
  storageQuery.data
@@ -354,6 +348,13 @@ const OxyAuthChooser: React.FC<OxyAuthChooserProps> = ({
354
348
  void logout();
355
349
  onComplete?.();
356
350
  },
351
+ customItems: (getAccountDialogConsumerHooks()?.menuItems ?? []).map((item) => ({
352
+ ...item,
353
+ onPress: () => {
354
+ onComplete?.();
355
+ item.onPress();
356
+ },
357
+ })),
357
358
  };
358
359
  }, [onComplete, showBottomSheet, logout]);
359
360
 
@@ -1,13 +1,12 @@
1
1
  import type React from 'react';
2
2
  import { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import {
4
- View,
5
- Pressable,
6
4
  StyleSheet,
7
5
  Platform,
8
6
  type StyleProp,
9
7
  type ViewStyle,
10
8
  } from 'react-native';
9
+ import { View, Pressable } from 'react-native-css/components';
11
10
  import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons';
12
11
  import { Avatar } from '@oxyhq/bloom/avatar';
13
12
  import * as Skeleton from '@oxyhq/bloom/skeleton';
@@ -18,6 +17,7 @@ import { useAuth } from '../hooks/useAuth';
18
17
  import { useOxy } from '../context/OxyContext';
19
18
  import { useI18n } from '../hooks/useI18n';
20
19
  import { registerAccountDialogConsumerHooks } from '../navigation/accountDialogManager';
20
+ import type { AccountDialogMenuItem } from '../navigation/accountDialogManager';
21
21
 
22
22
  const isWeb = Platform.OS === 'web';
23
23
 
@@ -30,7 +30,6 @@ const isWeb = Platform.OS === 'web';
30
30
  */
31
31
  const BG_TRANSITION_MS = 150;
32
32
  const AVATAR_TRANSITION_MS = 250;
33
- const OPACITY_TRANSITION_MS = 150;
34
33
  const LEAVE_DELAY_MS = 50;
35
34
 
36
35
  /** Shrunk-avatar scale on hover, matching Bluesky's `scale: 2/3`. */
@@ -64,6 +63,8 @@ export interface ProfileButtonProps {
64
63
  onAddAccount?: () => void;
65
64
  /** Optional: navigate to the signed-in user's own profile. */
66
65
  onNavigateProfile?: () => void;
66
+ /** App-owned actions rendered in the shared account menu. */
67
+ menuItems?: readonly AccountDialogMenuItem[];
67
68
  /**
68
69
  * Retained for source compatibility. The trigger now opens the unified
69
70
  * `OxyAccountDialogScreen` (a centered / bottom-sheet modal) rather than an
@@ -106,6 +107,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
106
107
  onNavigateManage,
107
108
  onAddAccount,
108
109
  onNavigateProfile,
110
+ menuItems,
109
111
  className,
110
112
  style,
111
113
  }) => {
@@ -139,8 +141,9 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
139
141
  onNavigateManage,
140
142
  onAddAccount,
141
143
  onNavigateProfile,
144
+ menuItems,
142
145
  });
143
- }, [onNavigateManage, onAddAccount, onNavigateProfile]);
146
+ }, [onNavigateManage, onAddAccount, onNavigateProfile, menuItems]);
144
147
 
145
148
  const avatarUrl = useMemo(
146
149
  () => (user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar, 'thumb') : undefined),
@@ -301,22 +304,11 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
301
304
  }
302
305
  : undefined;
303
306
 
307
+ // Expanded mode always shows identity + chevron (console/inbox wide sidebar).
308
+ // Hover only animates the row background and avatar shrink — not text visibility.
304
309
  const identityStyle: ViewStyle | undefined = isWeb
305
310
  ? {
306
311
  marginLeft: -resolvedAvatarSize / 2,
307
- opacity: active ? 1 : 0,
308
- transitionProperty: 'opacity',
309
- transitionDuration: `${OPACITY_TRANSITION_MS}ms`,
310
- transitionDelay: active ? '0ms' : `${LEAVE_DELAY_MS}ms`,
311
- }
312
- : undefined;
313
-
314
- const chevronStyle: ViewStyle | undefined = isWeb
315
- ? {
316
- opacity: active ? 1 : 0,
317
- transitionProperty: 'opacity',
318
- transitionDuration: `${OPACITY_TRANSITION_MS}ms`,
319
- transitionDelay: active ? '0ms' : `${LEAVE_DELAY_MS}ms`,
320
312
  }
321
313
  : undefined;
322
314
 
@@ -346,7 +338,7 @@ const ProfileButton: React.FC<ProfileButtonProps> = ({
346
338
  </Text>
347
339
  ) : null}
348
340
  </View>
349
- <View style={chevronStyle}>
341
+ <View>
350
342
  <MaterialCommunityIcons
351
343
  name="dots-horizontal"
352
344
  size={18}
@@ -25,7 +25,7 @@
25
25
 
26
26
  import type React from 'react';
27
27
  import { Fragment, useCallback, useState } from 'react';
28
- import { Pressable, View } from 'react-native';
28
+ import { Pressable, View } from 'react-native-css/components';
29
29
  import Animated, {
30
30
  Extrapolation,
31
31
  interpolate,
@@ -212,6 +212,7 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
212
212
  // compact header it is not represented anywhere else once the hero moved out
213
213
  // of the card, and the list is where you switch back to it.
214
214
  const switchableAccounts = current ? [current, ...others] : others;
215
+ const switchableAccountKey = switchableAccounts.map((account) => account.accountId).join(':');
215
216
  const facepile: AvatarGroupItem[] = switchableAccounts.map((account) => ({
216
217
  id: account.accountId,
217
218
  uri: account.avatarUrl ?? undefined,
@@ -381,61 +382,66 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
381
382
  {/* SWITCH ACCOUNT — own NativeWind card, NOT a Bloom grouped section: it
382
383
  is a disclosure header over an animated list, not a list of settings
383
384
  rows, and the grouped section is not a bare card container. */}
384
- <Animated.View className="bg-fill overflow-hidden mb-space-16" style={switchCardStyle}>
385
- <HoverPressable
386
- baseClassName={`flex-row items-center gap-space-12 px-space-12 py-[10px] min-h-[44px]${
387
- switchingDisabled ? ' opacity-60' : ''
388
- }`}
389
- hoverClassName="bg-fill-secondary"
390
- onPress={toggleExpanded}
391
- disabled={switchingDisabled}
392
- accessibilityRole="button"
393
- // The ARIA prop, not `accessibilityState`: react-native-web 0.21
394
- // forwards only this one, and RN maps it to
395
- // `accessibilityState.expanded` natively so the disclosure state
396
- // reaches assistive tech on BOTH platforms.
397
- aria-expanded={expanded}
398
- accessibilityLabel={switchLabel}
399
- >
400
- <Text className="flex-1 text-body text-text" numberOfLines={1}>
401
- {switchLabel}
402
- </Text>
403
- {/* The facepile previews who you can switch to; once the list is open
404
- it would just restate the rows below it. */}
405
- {expanded ? null : (
406
- <AvatarGroup
407
- items={facepile}
408
- layout="stack"
409
- size={FACEPILE_AVATAR_SIZE}
410
- max={FACEPILE_MAX}
411
- overlap={FACEPILE_OVERLAP}
412
- variant="thumb"
413
- showInitials
414
- ringColor={theme.colors.card}
415
- />
416
- )}
417
- <View style={[styles.chevronCircle, { backgroundColor: theme.colors.contrast50 }]}>
418
- <Animated.View style={chevronStyle}>
419
- <MaterialCommunityIcons name="chevron-down" size={20} color={theme.colors.text} />
420
- </Animated.View>
421
- </View>
422
- </HoverPressable>
385
+ <View className="mb-space-16">
386
+ <Animated.View style={[{ overflow: 'hidden' }, switchCardStyle]}>
387
+ <View className="bg-fill">
388
+ <HoverPressable
389
+ baseClassName={`flex-row items-center gap-space-12 px-space-12 py-[10px] min-h-[44px]${
390
+ switchingDisabled ? ' opacity-60' : ''
391
+ }`}
392
+ hoverClassName="bg-fill-secondary"
393
+ onPress={toggleExpanded}
394
+ disabled={switchingDisabled}
395
+ accessibilityRole="button"
396
+ // The ARIA prop, not `accessibilityState`: react-native-web 0.21
397
+ // forwards only this one, and RN maps it to
398
+ // `accessibilityState.expanded` natively — so the disclosure state
399
+ // reaches assistive tech on BOTH platforms.
400
+ aria-expanded={expanded}
401
+ accessibilityLabel={switchLabel}
402
+ >
403
+ <Text className="flex-1 text-body text-text" numberOfLines={1}>
404
+ {switchLabel}
405
+ </Text>
406
+ {/* The facepile previews who you can switch to; once the list is open
407
+ it would just restate the rows below it. */}
408
+ {expanded ? null : (
409
+ <AvatarGroup
410
+ items={facepile}
411
+ layout="stack"
412
+ size={FACEPILE_AVATAR_SIZE}
413
+ max={FACEPILE_MAX}
414
+ overlap={FACEPILE_OVERLAP}
415
+ variant="thumb"
416
+ showInitials
417
+ ringColor={theme.colors.card}
418
+ />
419
+ )}
420
+ <View style={[styles.chevronCircle, { backgroundColor: theme.colors.contrast50 }]}>
421
+ <Animated.View style={chevronStyle}>
422
+ <MaterialCommunityIcons name="chevron-down" size={20} color={theme.colors.text} />
423
+ </Animated.View>
424
+ </View>
425
+ </HoverPressable>
423
426
 
424
- {/* Animated reveal: an overflow-clipped container whose height + opacity
425
- are driven by `listProgress`; the inner style-based wrapper measures
426
- the natural content height via `onLayout`. */}
427
- <Animated.View
428
- style={[styles.collapse, listStyle]}
429
- pointerEvents={expanded ? 'auto' : 'none'}
430
- >
431
- <View
432
- style={styles.collapseMeasure}
433
- onLayout={(event) => setListContentHeight(event.nativeEvent.layout.height)}
427
+ {/* Animated reveal: an overflow-clipped container whose height + opacity
428
+ are driven by `listProgress`; the inner style-based wrapper measures
429
+ the natural content height via `onLayout`. */}
430
+ <Animated.View
431
+ style={[styles.collapse, listStyle]}
432
+ pointerEvents={expanded ? 'auto' : 'none'}
434
433
  >
435
- <View>{listItems}</View>
434
+ <View
435
+ key={switchableAccountKey}
436
+ style={styles.collapseMeasure}
437
+ onLayout={(event) => setListContentHeight(event.nativeEvent.layout.height)}
438
+ >
439
+ <View>{listItems}</View>
440
+ </View>
441
+ </Animated.View>
436
442
  </View>
437
443
  </Animated.View>
438
- </Animated.View>
444
+ </View>
439
445
 
440
446
  {/* OXY STORAGE — own NativeWind card for the same reason: a title, a meter
441
447
  and two chips are not settings rows. Its cloud sits in the same 20px
@@ -484,6 +490,14 @@ const AccountsMenuView: React.FC<AccountsMenuViewProps> = ({
484
490
  title={t('accountMenu.help')}
485
491
  onPress={menu.onHelp}
486
492
  />
493
+ {menu.customItems.map((item) => (
494
+ <SettingsListItem
495
+ key={item.key}
496
+ icon={<MenuIcon name={(item.icon ?? 'dots-horizontal') as keyof typeof MaterialCommunityIcons.glyphMap} theme={theme} />}
497
+ title={item.label}
498
+ onPress={item.onPress}
499
+ />
500
+ ))}
487
501
  <SettingsListItem
488
502
  icon={<MenuIcon name="logout" theme={theme} />}
489
503
  title={t('accountMenu.signOut')}
@@ -6,7 +6,7 @@
6
6
 
7
7
  import type React from 'react';
8
8
  import { useState } from 'react';
9
- import { Pressable, View } from 'react-native';
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
12
  import { Text } from '@oxyhq/bloom/typography';
@@ -62,6 +62,12 @@ export interface AccountsMenuActions {
62
62
  onPrivacy: () => void;
63
63
  onTerms: () => void;
64
64
  onSignOut: () => void;
65
+ customItems: readonly {
66
+ key: string;
67
+ label: string;
68
+ icon?: string;
69
+ onPress: () => void;
70
+ }[];
65
71
  }
66
72
 
67
73
  /**
@@ -79,8 +79,8 @@ export interface AuthState {
79
79
  export interface AuthActions {
80
80
  /**
81
81
  * Sign in
82
- * - Web: Redirects to auth.oxy.so (no public key needed)
83
- * - Native: Uses cryptographic identity from keychain
82
+ * - Web + native: opens the in-app Oxy account dialog (`openAccountDialog('signin')`).
83
+ * Never navigates to auth.oxy.so that origin is third-party OAuth only.
84
84
  *
85
85
  * @param publicKey - Native: identity public key. Ignored on web.
86
86
  */
@@ -105,6 +105,8 @@ export interface AuthActions {
105
105
  export interface UseAuthReturn extends AuthState, AuthActions {
106
106
  /** Access to full OxyServices instance for advanced usage */
107
107
  oxyServices: ReturnType<typeof useOxy>['oxyServices'];
108
+ /** Switch the device session to another account on this device */
109
+ switchToAccount: ReturnType<typeof useOxy>['switchToAccount'];
108
110
  /** Open a bottom sheet screen (e.g. 'ManageAccount', 'FileManagement') */
109
111
  showBottomSheet: ReturnType<typeof useOxy>['showBottomSheet'];
110
112
  /** Open the avatar picker bottom sheet */
@@ -117,8 +119,8 @@ export interface UseAuthReturn extends AuthState, AuthActions {
117
119
  * Features:
118
120
  * - Zero config: Just wrap with OxyProvider and use
119
121
  * - Cross-platform: Same API on native and web
120
- * - Auto session restore: Web apps silently restore via device-secret mint, then
121
- * silent OAuth against the IdP hub; native apps use the shared keychain
122
+ * - Auto session restore: device-first cold boot (`deviceId` + `deviceSecret` mint);
123
+ * never auto-redirects to the IdP
122
124
  * - Type-safe: Full TypeScript support
123
125
  */
124
126
  export function useAuth(): UseAuthReturn {
@@ -137,6 +139,7 @@ export function useAuth(): UseAuthReturn {
137
139
  logoutAll,
138
140
  refreshSessions,
139
141
  oxyServices,
142
+ switchToAccount,
140
143
  hasIdentity,
141
144
  getPublicKey,
142
145
  showBottomSheet,
@@ -201,6 +204,7 @@ export function useAuth(): UseAuthReturn {
201
204
 
202
205
  // Advanced
203
206
  oxyServices,
207
+ switchToAccount,
204
208
  showBottomSheet,
205
209
  openAvatarPicker,
206
210
  };
@@ -12,6 +12,14 @@
12
12
 
13
13
  import type { AccountDialogView } from '@oxyhq/core';
14
14
 
15
+ /** An app-owned action rendered inside the shared account menu. */
16
+ export interface AccountDialogMenuItem {
17
+ key: string;
18
+ label: string;
19
+ icon?: string;
20
+ onPress: () => void;
21
+ }
22
+
15
23
  /**
16
24
  * Live open/close handles registered by the mounted provider. `open` presents
17
25
  * the `AccountDialog` surface (and points its view); `close` dismisses it. Both
@@ -31,6 +39,7 @@ export interface AccountDialogConsumerHooks {
31
39
  onNavigateManage?: () => void;
32
40
  onAddAccount?: () => void;
33
41
  onNavigateProfile?: () => void;
42
+ menuItems?: readonly AccountDialogMenuItem[];
34
43
  }
35
44
 
36
45
  let controls: AccountDialogControls | null = null;
@@ -17,8 +17,18 @@ import { toast } from '@oxyhq/bloom/toast';
17
17
 
18
18
  type UsernameStatus = 'idle' | 'checking' | 'available' | 'taken' | 'invalid';
19
19
 
20
- /** Kind of account this screen can create. `personal` is a signup-minted root and is never created here. */
21
- type CreatableAccountKind = Exclude<AccountKind, 'personal'>;
20
+ /**
21
+ * Kind of account this screen can create.
22
+ *
23
+ * A strict subset of what `POST /accounts` accepts, not `Exclude<AccountKind,
24
+ * 'personal'>`: this screen SWITCHES INTO the account it just created, so it can
25
+ * only offer kinds an operator may act as. `personal` is a signup-minted root,
26
+ * and a `channel` is a content identity nobody occupies — creating one is
27
+ * Mention's job, through the API. Spelling the subset out here is what keeps a
28
+ * newly-added kind from silently inheriting the `project` label in
29
+ * {@link kindLabel}'s fallback.
30
+ */
31
+ type CreatableAccountKind = Extract<AccountKind, 'organization' | 'project' | 'bot'>;
22
32
 
23
33
  const USERNAME_REGEX = /^[a-zA-Z0-9_-]{3,30}$/;
24
34
  const DEBOUNCE_MS = 400;