From 9a9d9188e76f4c8ada7077bf62a6c64e613f7d75 Mon Sep 17 00:00:00 2001 From: msfstef Date: Thu, 18 Jun 2026 16:52:31 +0300 Subject: [PATCH 1/4] Harden agents-mobile store config: privacy manifest, permissions, splash, icons Cover binary-readiness gaps for App Store / Play review: - Declare iOS privacy manifest (ios.privacyManifests) for the required-reason APIs used by AsyncStorage and Sentry. Apple auto-rejects uploads (ITMS-91053) that call these without a declaration; statically-linked pods' own manifests aren't reliably read, so declare at app level. NSPrivacyTracking is false and crash/perf data is declared not-linked / not-tracking. - Set expo-image-picker microphonePermission:false and block RECORD_AUDIO + legacy READ/WRITE_EXTERNAL_STORAGE on Android. The app uses the system photo picker (content URIs, no broad media access) and never records audio, so these only bloat the Play permission list / Data Safety form. - Add expo-splash-screen + assets/splash-icon.png (dark #101217 background). There was no splash, so cold start flashed blank white against the dark theme. - Ship an opaque iOS icon (flatten the transparent corners onto #101217) since Apple rejects icons with alpha, and add a monochrome adaptive-icon layer for Android 13+ themed icons. - Add expo-system-ui so userInterfaceStyle:automatic actually applies on Android (prebuild warned it was a no-op without it). - Add an app description. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/mobile-store-readiness-polish.md | 5 ++ packages/agents-mobile/app.config.ts | 79 +++++++++++++++++- .../assets/adaptive-icon-monochrome.png | Bin 0 -> 10476 bytes packages/agents-mobile/assets/icon.png | Bin 19801 -> 12232 bytes packages/agents-mobile/assets/splash-icon.png | Bin 0 -> 10261 bytes packages/agents-mobile/package.json | 2 + pnpm-lock.yaml | 40 +++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 .changeset/mobile-store-readiness-polish.md create mode 100644 packages/agents-mobile/assets/adaptive-icon-monochrome.png create mode 100644 packages/agents-mobile/assets/splash-icon.png diff --git a/.changeset/mobile-store-readiness-polish.md b/.changeset/mobile-store-readiness-polish.md new file mode 100644 index 0000000000..27ee38a1f4 --- /dev/null +++ b/.changeset/mobile-store-readiness-polish.md @@ -0,0 +1,5 @@ +--- +'@electric-ax/agents-mobile': patch +--- + +Polish the mobile app for App Store / Play review: declare the iOS privacy manifest for required-reason APIs (AsyncStorage + Sentry), drop the unused microphone permission and block legacy Android storage permissions, add a splash screen, ship an opaque iOS icon and a monochrome Android adaptive-icon layer, add `expo-system-ui` so `userInterfaceStyle` applies on Android, wrap the app in a recoverable error boundary, add a timeout escape hatch to the OAuth callback, and keep auth diagnostics out of production logs. diff --git a/packages/agents-mobile/app.config.ts b/packages/agents-mobile/app.config.ts index 93bdd47dfa..57a7bf68e7 100644 --- a/packages/agents-mobile/app.config.ts +++ b/packages/agents-mobile/app.config.ts @@ -20,6 +20,7 @@ export default ({ config }: ConfigContext): ExpoConfig => slug: `agents-mobile`, owner: `electric-ax`, scheme: `electric-agents`, + description: `Mobile client for Electric Agents — connect to your agents servers and chat with running agents.`, version: packageJson.version, runtimeVersion: packageJson.version, orientation: `portrait`, @@ -29,6 +30,21 @@ export default ({ config }: ConfigContext): ExpoConfig => plugins: [ `expo-router`, `expo-web-browser`, + // Branded launch screen so cold start shows the logo on the + // app's dark background instead of a blank white flash. + [ + `expo-splash-screen`, + { + backgroundColor: `#101217`, + image: `./assets/splash-icon.png`, + imageWidth: 200, + resizeMode: `contain`, + dark: { + backgroundColor: `#101217`, + image: `./assets/splash-icon.png`, + }, + }, + ], // The chat WebView (Expo DOM / streamdown) ships regex lookbehind, // which JavaScriptCore only parses on iOS 16.4+. Below that the whole // DOM bundle fails to parse and the chat renders blank. @@ -45,8 +61,11 @@ export default ({ config }: ConfigContext): ExpoConfig => [ `expo-image-picker`, { - photosPermission: `Allow Electric Agents to attach photos to messages.`, - cameraPermission: `Allow Electric Agents to take a photo to attach to a message.`, + photosPermission: `Electric Agents accesses your photo library so you can attach an existing photo to a chat message.`, + cameraPermission: `Electric Agents uses your camera so you can take a photo to attach to a chat message.`, + // The app never records audio; `false` drops the RECORD_AUDIO + // (Android) + NSMicrophoneUsageDescription (iOS) the plugin adds. + microphonePermission: false, }, ], ], @@ -59,14 +78,70 @@ export default ({ config }: ConfigContext): ExpoConfig => ITSAppUsesNonExemptEncryption: false, }, supportsTablet: true, + // Apple rejects uploads (ITMS-91053) that call "required reason" + // APIs without declaring them, and doesn't reliably read + // statically-linked pods' own manifests — so declare app-level. + // UserDefaults + file timestamp come from AsyncStorage / RN core; + // system boot time + disk space from Sentry. + privacyManifests: { + NSPrivacyTracking: false, + NSPrivacyTrackingDomains: [], + NSPrivacyCollectedDataTypes: [ + { + NSPrivacyCollectedDataType: `NSPrivacyCollectedDataTypeCrashData`, + NSPrivacyCollectedDataTypeLinked: false, + NSPrivacyCollectedDataTypeTracking: false, + NSPrivacyCollectedDataTypePurposes: [ + `NSPrivacyCollectedDataTypePurposeAppFunctionality`, + ], + }, + { + NSPrivacyCollectedDataType: `NSPrivacyCollectedDataTypePerformanceData`, + NSPrivacyCollectedDataTypeLinked: false, + NSPrivacyCollectedDataTypeTracking: false, + NSPrivacyCollectedDataTypePurposes: [ + `NSPrivacyCollectedDataTypePurposeAppFunctionality`, + ], + }, + ], + NSPrivacyAccessedAPITypes: [ + { + NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryUserDefaults`, + NSPrivacyAccessedAPITypeReasons: [`CA92.1`], + }, + { + NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryFileTimestamp`, + NSPrivacyAccessedAPITypeReasons: [`C617.1`], + }, + { + NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategorySystemBootTime`, + NSPrivacyAccessedAPITypeReasons: [`35F9.1`], + }, + { + NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryDiskSpace`, + NSPrivacyAccessedAPITypeReasons: [`E174.1`], + }, + ], + }, }, android: { ...config.android, package: applicationId, versionCode, edgeToEdgeEnabled: true, + // expo-image-picker declares these, but the app uses the system + // photo picker (content URIs) and never records audio — block them + // so the AAB permission list / Play Data Safety form stays clean. + blockedPermissions: [ + `android.permission.RECORD_AUDIO`, + `android.permission.READ_EXTERNAL_STORAGE`, + `android.permission.WRITE_EXTERNAL_STORAGE`, + ], adaptiveIcon: { foregroundImage: `./assets/adaptive-icon.png`, + // Android 13+ themed (monochrome) icons — white silhouette + // the launcher tints to match the user's wallpaper. + monochromeImage: `./assets/adaptive-icon-monochrome.png`, backgroundColor: `#101217`, }, }, diff --git a/packages/agents-mobile/assets/adaptive-icon-monochrome.png b/packages/agents-mobile/assets/adaptive-icon-monochrome.png new file mode 100644 index 0000000000000000000000000000000000000000..f66902f11e1d9c33bc5e2e68694af04ccaaac0e5 GIT binary patch literal 10476 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 LWOmZ5)qDRBahYif literal 19801 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 AhyVZp diff --git a/packages/agents-mobile/assets/splash-icon.png b/packages/agents-mobile/assets/splash-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6795ff511e4675397ac66c1d1a1f32fa8f798d87 GIT binary patch literal 10261 zcmeHM`(Mo2|36by5))d*ZcVLKa%*y3q#0VbLT$7z*_vvXmQ?6QY2Kki5uYNfrP(iI zu`Us5r5fqNVrzX`@<~cXx=^ENs_FgyKF@pX=YRM-9@{USbKd8??)TZ}v3$`G1 zkReMJf8|NY04xU(BLn=CTTa+bh~2X#U(H(?_0yZ0>g?}U`PHhg_qI8|d~iBcIo@7U z-{`RTrOlCBWsY@KQt@r0L0ioA1}vVv$XvSj#ieGeCDx`!TaILnIrXJ^P}{L*K{0N> z--{ma?qut5<>H?E51O}qv(ez^#mBwd|H_ZPc)h!+v+kbt$*=q!ISJ#GO#LC&%jTtrxzMUz0J0v~w`&U~}hSf6RQHJ=MtVul3zzym)U7Zf( z{y#rjo}O#))7{m!`#{1A4Rf1+o^>?cX8C_#DbE@>Yoj9Fz;EZ z?*LM_qgc|o#Qua+-wL^p#xSUO5FxVAdeOTE`K!&G>Zk>{n72<}HIXfksG0f9MfRx) zAu1Uj4dYa%bZxC$d)&Ou{!Ub<8@Q&Fqku?~vYBs$fLsy+eNS&nMq_F_qv8`6SoOucA|GHt$5`|RrQ+E8KKw_@CW zS}gg_HNAy<{4%!tShP{wez1S{AeU}+BIZst&!(9_u3c@Aly(^05biMLr1JnwVXhYb zSS;~4hU=>{G>H#=G);3j=@N=(QoU$K5?2!2y`bB4+&w&Am##@%S}!`gcvY_+VQgbt z+FNfA<`g2eaAhf#%?VB6pPP4C0h|qJ4qX5`_sJEWfK@j|xAuPyJhvEtY4+?djBeh{ zNtHhV^2sr*UxS9*6>U`cJ4N(4udC61SVM%vIjIZ8Dc*9DSLd`+9FMToUHJ}5*f*Rz zv_z|a0BTl!)xaqi+4&B3e73rGt-?Ot=Y!~Z1JEQ<0Do#y9G|Vkd5OliEi&b3ff@4| zs5@rL3qh<$*G9VaaJnYC_2*by%+jr=eA_&j5CiU9;bj8{?e5rOiFiH~snRrX2b7lw z9{-$;J51>?+9$vJ1sgXET2y^uOYbu+wYh2Zdzosq3MvW0*_n*m%U#Y2Aq!n?0d!&2DjL z_R5(F`&Y4di1h1v(N@VE`}mFm80B7XqYf%5>1Jp&HlkRfnhpn2*z$LPcs9uLNJ^`} z-O~7VZBOF!OiQ&e%Yaf~#yReZgsv#U)fqtOI)XdY_f*{|QW)ZU+83-%clH=ou%o4y zmB5E!Jl)x;%jZXNvn1~gE0RV4@Gec_vIZ_Y*hVU$H2j#^+ZWSOBzfKUbQniI4Mm~z zdog!0*r}_x4fGFz&}6IrrgJ3dl$rm%qi!z?{7AKoH>)qy!rwAan9a|vsb&Q^Ww1_{ zVO-YNT89sL<+<^sIsmG%Ic+BV#xAonNB+8>;?#G9VMx^?m#FlN!TB|<_arJYB#^ZX zPS>M-mR;3u6*^du01sd`1K7Q$MOuB62LN4d&HAO5BS=)m1)Ix95R2(*;ioY%kMiBh zSV2S)?4$~+1hfARwowIxSZjFg5NGFlk?aW&9CFdR%uowG5+KK3_{}n{epyVwyICv=de;LPA82;)0K3B$=WR`Oz`-nIMC)inMB1XHTQPJ_5#*Fbkg*ya9rSBza%1Lp_ceoBFo zEItLp0V!NJwci8SQvu)|R9Ls?5ioOna^y2`--i2I{e+*Q3}QBcNslADPc^vjsFr+Z%8_-6 zfP21Pk`Nlw+{!>_&C(PW!5n2o| z{yK&lDR6iQQY*6#!jE`c_gXUos^|wpGyDMdE3I6$IT4mZGN6g9sXFF!Y=U22)+0}B zSQ~fa7jZ9R+O4{U%ra^_bvnpFslbAQfO20o1KjV1Ge8^SJ;8?x$CK2^s4%U%ZZlT_ zr;v?97!t44$}29$Q%o)tlUKdy>IsBsl7L_?+k^l_W_hB`Kx>%_is!RD0;twT=Uiq)W(39))*KHKZ88w4Vdk^exbR zcs=KQV^A+tUs(#gUOMK_9)=($z+*Qz|H2Y}cOQ`>XWfJDp*0e+5!}@c1a~7ivhpFG z!PNLQ%uY4da8cQYTW>^^m*Y@!ou{_U)Vb{&X$bVSzW9+jk*GnAJ@v& zD2%EK?hO7l&m}oCBIM_p02T-n5u^UO3S}$=yW-D3uFF>;hoeG0X;$QNzvsCnm2bu(%ew_&zWXL z8;;hrMIfNd=Hluuz>N<2^I`BO9LaL_6_9bdY(hrC69xhi^k5C=la=WR$Pz#&Lv=cl zx%lWAWsLz#kqH**!C!Xau52*g&9UYk+3kMthvmpJ%)=gIqEhL^o%JoS3UtOS>%(Pz zogeTiTHloS4^=46y(2>X!V_=wyT~D%1ZbdYtG3Ld{I_!z@FwO3*v91cEqfH0Fk=ugg*{uU$I;F(ijT9fR{OC?%~;yz2_uk9I{uI0blR^XyN|Mg+Bf!R%9O2qw=_jJ-pt-f?FSX;Fv)bD*P3Md*E&qBI&m=tPf#|b!>xu;G4jUoo>Q&1@3=%M@EFVN=F9Pwhnc;@Z5ZU6eNalKt>}s*}kkRYB_3Ag!+uC zR#mbA4G~%x+aa;0#TpR1x`jy*ND$l?LQ#HoC4Q$-P|!; zs@SfRbp!}_qaz?Wa|Dt8TPG$lOkAGMCItYicvvb=esRV0)a5Djgkbdy zM=oY}HIzDYD6UWP+BXJXzDMDe6D_^k?T8#%C(D6})h?Dh+ zjz8HVAT5R1hvUJ}QG_}0QBIby5rkQKE)^IM>m=RVaR{kh5l?#l%2;8NQfARVXFxAX z9_c)`!Sb~wXXhBRM(YAcC*vj_Eg%Z`?G$B47vz*M)$tO%Sz!iMT>pr|^-F=SOSjjS zb~k0ubhd1`f-sZzY5)mEXEEh;nx~QQr{m?pl!i%Q=2Amxun`ZNm zKP=*m?fc75UFz#h^w-t~ z+naI&BHr43Waz;8r#yS_r)Cy&@<}`GrTvftv(L?5d*aa(5@g27-?D;M+7jAME{!_h zHJ$XIPGiT%=ci+sA+!TMihK=MvaL^Dm{u@SF$py!?Vb&3?MN5{OmL|OwA`9ZH(%ol-Pw5M2LGzQ1sFN)&J+wF%xGoT?$oUA`%O+ghB>t7 zCugU!zFUGNv6nFyYv3^)2L@}3;p_reiIHD>I_@UOuxIwy%dd#IEPAtXEQ3US=L&a> zz4zO18pDK!0l2Yp7}qK35b9a{OMsm###BR*_0S?6^6d3{*C?v8;n|aF7paA9bR+9Zic3b7ajsd*q=j;{K!ggx*L< zi%s+IJ)+bYj;E6n66C;g`5A-wF%iiC5o2No;&`hvjQ!%wlarl|qCkW_WKR)ydDhHK zdD)NwYh~Co1;vsIYpRGY8#ZnsOpnOEwLc+r>^~0;(KeoBC8RxyPf`~iE5|GSwLF{C zQId3_B)s;Zoahl%c)g^g>sL2-Hm($51B2Cl+_ui1kh~?*Rtg~$>7DQR$gRB^!%{j| zBjwm2!o@g ziBcS2sq#@(YWK69>^D;&Smo{Xg&siKr&F;fPII6Xn6 zrp92gOEfv27tTUrTqPk8ufzGV?x81E2N(6=SA0&+(>fusC z*7pCwWgJCSa^WHMhqVW|5IVP`V;B;cs4=`t8B`3xcw-EGxOV%C+6>D+!@C>k@ji`j zSWPO0`aS*FG8XfhQmRRh&PcZQYFDF3Hi6oz;+01JfAE{+cxpQ<$11~yV?gzoN<`|V zmLOg3ro4N2X*1v41Pdb>M2GXjrOJmGZUCC#I6*ZI13PyCQ)_-s5Av3-y=d@9ZJW~4m;q8Gw0iF40eK;BTs z=|ow!j(=o(rb~eKpcKU$QGKtLM-=v@RqKg)o-j-nl%ifDD)-Y}^XM&pqogz6%&-ZB z--^QlYxqs{lSDepUMZY3x5x0<0Lt4LOD=QVPD{e;O19tgCWiA%lR&j>qvy>Kj4p1W zILY#c^cZ^-5UDT6G8ji6rb89zg!d`gY&CZUGZE=5dh0OEXB~|u7TIZUyBkFeA8i7T zJnDzyPAMJqnb}cbTxFz8bG~ZE5vLR?xQ1kIDmpOcCY9X6ueAE1(|KTvZJg>*=B%RO z#KKKm-u>1d#L{{kgQ09RxUE$C*WR@G7eq!jClN&~eTHEWdHnby_X|uv$*jn~86f6T zYp7VSt@Y}iO#&&0ib-y2p_Km2BAs(Ispq*LeZP#ppMg4mMm^>9h?~t%-mp+r0(G3T z5?1KyYpB!DN>{CZ7Jq&ATw01%Q>M?B?*^n3N(Vh=3#F_Q!)mcnAtH~*J~Y0Fr2>n+ zl;QWqoF!#gO+72EJ4<6I3#6#8TKPQa4(=8Tn4hU5{SKM8(Xq3_1l{fO9%Ko|(#z)+S>4JNO%3ccufRxs{1O;r)r@f}L!6ooa_c*F>< z9if2v7rm(uZ>%iBLi|%1Y(g=T(%|Prdp1nHq?72KYS{bPgP=i{hhE5w5*)O|_{IHS zFt}h`DU~^9d^5Gd-)W1BfKEV&%muokqhvCp^Aj)~i0+wK*Ycea1x$NVGRo7)7ftT9 zVyq7hqadm&4Pz+{RzP);qC$J8UoizrQjNy-ZEYgvC`I`=iM!GBhlC$?kafEuzKIm% zs_t+qVvH<^>d4Oil{2G%4d4*yfW2-jNcxi>l1jZiqgwhppH`nds6LHAsG31zUq_Lq zP~B{iQe#F+O93nNYh!oR!nAr!K7#MZZlx84_`pydFWJhc5{pSdVtU4k`=u%S(-H{@ zpfUK&g!m8`-x(^>r7MhUXHU<}zn%K`0V0BI7s#aZS7J+l`&o*ZE-Ru((@u9>YlpGW z&@CIUmhU#lAdKHsdKz={wfYQ5u-LB6JX!A34s(NSnZqEhV?KI7l6(-I)cx>PngBxH zxW2e?^2hSv)up*Kk9mxM)*1EU7dP0A&e8qp{!aWU9o5oFE4g$JHp0A8nz1jZBJrrE zQ&}Y2u=0mj+7|goqTS8u|MlE#&KZJ&9~71Sp*gi!q^k ztDFZe6!d6|eSB?Xn{HM?_Lp(ZJ#AhEokD&PK}w*5D$KKp$3o-U_l+1sP_o*5jBDpY zX-FWtuUX!Z(SDi_^HPfgbXmEx60x#^gPXF`q(=bP_?bP_L5B6v9)yYlCX8-Nq(p zIOouP0z-z~P#m2AN~5W896;NDXw2HNSU#MNY^L4A=7N3ylGh)63ihhG-0w++6$OiN z3b{ztk8!X-WT`gorFC521f)Qfi*xk7*K-Qi^aL5~)f<)o70VEMCr+VjKU( zw#D4&ez!;qRLKVs16A3NGW&C@qo0m(bZ$6`Um0k)0ICr(yu63n7TbmGIcYwgy`!=G o=kFg0{3C(?SqWGN{l)3K3>w6Iv+l{Sl#3+`mVb3=zHHb318Wkx%>V!Z literal 0 HcmV?d00001 diff --git a/packages/agents-mobile/package.json b/packages/agents-mobile/package.json index 0c9bdb6218..7ede496a23 100644 --- a/packages/agents-mobile/package.json +++ b/packages/agents-mobile/package.json @@ -33,7 +33,9 @@ "expo-image-picker": "~17.0.11", "expo-linking": "~8.0.12", "expo-router": "~6.0.24", + "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", + "expo-system-ui": "~6.0.9", "expo-web-browser": "~15.0.11", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a71eea6119..01db79712b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1746,9 +1746,15 @@ importers: expo-router: specifier: ~6.0.24 version: 6.0.24(@expo/metro-runtime@6.1.2)(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(expo-constants@18.0.13)(expo-linking@8.0.12)(expo@54.0.35)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-splash-screen: + specifier: ~31.0.13 + version: 31.0.13(expo@54.0.35)(typescript@5.9.3) expo-status-bar: specifier: ~3.0.9 version: 3.0.9(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-system-ui: + specifier: ~6.0.9 + version: 6.0.9(expo@54.0.35)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) expo-web-browser: specifier: ~15.0.11 version: 15.0.11(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) @@ -14307,6 +14313,11 @@ packages: resolution: {integrity: sha512-mcmyML3oXcqFUXUxtdtCL1O00ztNI2v76d+MdniXRUgHNxIcHZ05zo+DqBaOOT6LQnPk4vA4YHqQl7iGUfRb3g==} engines: {node: '>=20.16.0'} + expo-splash-screen@31.0.13: + resolution: {integrity: sha512-1epJLC1cDlwwj089R2h8cxaU5uk4ONVAC+vzGiTZH4YARQhL4Stlz1MbR6yAS173GMosvkE6CAeihR7oIbCkDA==} + peerDependencies: + expo: '*' + expo-status-bar@2.2.3: resolution: {integrity: sha512-+c8R3AESBoduunxTJ8353SqKAKpxL6DvcD8VKBuh81zzJyUUbfB4CVjr1GufSJEKsMzNPXZU+HJwXx7Xh7lx8Q==} peerDependencies: @@ -14319,6 +14330,16 @@ packages: react: '*' react-native: '*' + expo-system-ui@6.0.9: + resolution: {integrity: sha512-eQTYGzw1V4RYiYHL9xDLYID3Wsec2aZS+ypEssmF64D38aDrqbDgz1a2MSlHLQp2jHXSs3FvojhZ9FVela1Zcg==} + peerDependencies: + expo: '*' + react-native: '*' + react-native-web: '*' + peerDependenciesMeta: + react-native-web: + optional: true + expo-web-browser@15.0.11: resolution: {integrity: sha512-r2LS4Ro6DgUPZkcaEfgt8mp9eJuoA93x11Jh7S6utFe0FEzvUNn2yFhxg8XVwESaaHGt2k5V8LuK36rsp0BeIw==} peerDependencies: @@ -37228,6 +37249,14 @@ snapshots: expo-server@1.0.7: {} + expo-splash-screen@31.0.13(expo@54.0.35)(typescript@5.9.3): + dependencies: + '@expo/prebuild-config': 54.0.8(expo@54.0.35)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + transitivePeerDependencies: + - supports-color + - typescript + expo-status-bar@2.2.3(react-native@0.80.1(@babel/core@7.28.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 @@ -37241,6 +37270,17 @@ snapshots: react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-system-ui@6.0.9(expo@54.0.35)(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + dependencies: + '@react-native/normalize-colors': 0.81.5 + debug: 4.4.3 + expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + optionalDependencies: + react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - supports-color + expo-web-browser@15.0.11(expo@54.0.35)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: expo: 54.0.35(@babel/core@7.29.0)(@expo/metro-runtime@6.1.2)(expo-router@6.0.24)(react-native-webview@13.15.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) From 4f31ffef158360b7daf940cff3b8a37bbf9dbddd Mon Sep 17 00:00:00 2001 From: msfstef Date: Thu, 18 Jun 2026 16:53:17 +0300 Subject: [PATCH 2/4] Add error boundary and OAuth callback timeout to agents-mobile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two robustness fixes for flows a store reviewer is likely to hit: - Wrap the app provider tree in Sentry.ErrorBoundary (inside ThemeProvider so the fallback is themed) with a "Try again" screen. An uncaught render error otherwise shows a blank/native crash screen, which is an automatic review rejection. Sentry still reports the error. - Give the /oauth/callback route a 10s escape hatch. A cold start onto the callback with no pending request could spin on "Finishing sign-in…" forever; now it surfaces a "Back to sign-in" action. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/agents-mobile/app/_layout.tsx | 67 +++++++++++++++++-- packages/agents-mobile/app/oauth/callback.tsx | 39 ++++++++++- 2 files changed, 99 insertions(+), 7 deletions(-) diff --git a/packages/agents-mobile/app/_layout.tsx b/packages/agents-mobile/app/_layout.tsx index 9a32273b2b..3e63334858 100644 --- a/packages/agents-mobile/app/_layout.tsx +++ b/packages/agents-mobile/app/_layout.tsx @@ -1,12 +1,13 @@ import { Redirect, Stack, usePathname } from 'expo-router' import { StatusBar } from 'expo-status-bar' -import { ActivityIndicator, StyleSheet, View } from 'react-native' +import { ActivityIndicator, StyleSheet, Text, View } from 'react-native' import { GestureHandlerRootView } from 'react-native-gesture-handler' import { SafeAreaProvider, initialWindowMetrics, } from 'react-native-safe-area-context' import { AgentsProvider } from '../src/lib/AgentsProvider' +import { PrimaryButton } from '../src/components/PrimaryButton' import { ThemeProvider, useColorSchemeMode, @@ -18,6 +19,7 @@ import { } from '../src/lib/MobileAppState' import { CloudAuthProvider } from '../src/lib/CloudAuthContext' import { isCallbackUrl } from '../src/lib/cloudAuth' +import { fontSize, lineHeight, spacing } from '../src/lib/theme' import { Sentry, initSentry } from '../src/lib/sentry' // Initialize early so startup crashes are captured (no-op in dev). @@ -28,17 +30,48 @@ function RootLayout(): React.ReactElement { - - - - - + {/* Inside ThemeProvider so the themed fallback turns an uncaught + render error into a recoverable screen, not a blank crash. */} + ( + + )} + > + + + + + + ) } +function RootErrorFallback({ + onRetry, +}: { + onRetry: () => void +}): React.ReactElement { + const tokens = useTokens() + return ( + + + + Something went wrong + + + The app hit an unexpected error. You can try again — if it keeps + happening, reopen the app. + + + + + + ) +} + export default Sentry.wrap(RootLayout) function RootNavigator(): React.ReactElement { @@ -115,4 +148,26 @@ const styles = StyleSheet.create({ alignItems: `center`, justifyContent: `center`, }, + fallback: { + flex: 1, + alignItems: `center`, + justifyContent: `center`, + paddingHorizontal: spacing.xl, + gap: spacing.md, + }, + fallbackTitle: { + fontSize: fontSize.xl, + fontWeight: `600`, + lineHeight: lineHeight.xl, + textAlign: `center`, + }, + fallbackBody: { + fontSize: fontSize.sm, + lineHeight: lineHeight.sm, + textAlign: `center`, + }, + fallbackAction: { + alignSelf: `stretch`, + marginTop: spacing.sm, + }, }) diff --git a/packages/agents-mobile/app/oauth/callback.tsx b/packages/agents-mobile/app/oauth/callback.tsx index bcd64bdf08..af642e4552 100644 --- a/packages/agents-mobile/app/oauth/callback.tsx +++ b/packages/agents-mobile/app/oauth/callback.tsx @@ -1,12 +1,13 @@ import { useEffect, useMemo, useRef, useState } from 'react' import * as Linking from 'expo-linking' -import { Redirect, useLocalSearchParams } from 'expo-router' +import { Redirect, useLocalSearchParams, useRouter } from 'expo-router' import { ActivityIndicator, StyleSheet, Text, View } from 'react-native' import { cloudAuth, isCallbackUrl, type CloudAuthState, } from '../../src/lib/cloudAuth' +import { PrimaryButton } from '../../src/components/PrimaryButton' import { useMobileAppState } from '../../src/lib/MobileAppState' import { useTokens } from '../../src/lib/ThemeProvider' import { fontSize, lineHeight, spacing } from '../../src/lib/theme' @@ -50,6 +51,7 @@ type RouteStatus = export default function OAuthCallbackRoute(): React.ReactElement { const params = useLocalSearchParams() + const router = useRouter() const tokens = useTokens() const styles = useMemo(() => createStyles(tokens), [tokens]) const { serverUrl, onboardingDismissed } = useMobileAppState() @@ -63,6 +65,17 @@ export default function OAuthCallbackRoute(): React.ReactElement { deriveStatus(cloudAuth.getState()) ) + // Escape hatch: a cold start onto `/oauth/callback` with no pending + // request (or a redirect that never arrives) would otherwise spin on + // "Finishing sign-in…" forever. After a grace period, surface a way + // back to the sign-in screen. + const [tookTooLong, setTookTooLong] = useState(false) + useEffect(() => { + if (status.kind !== `working`) return + const timer = setTimeout(() => setTookTooLong(true), 10_000) + return () => clearTimeout(timer) + }, [status.kind]) + useEffect(() => { // Sync once on mount in case the state changed between the // `useState` initializer and this effect attaching (very narrow @@ -137,6 +150,18 @@ export default function OAuthCallbackRoute(): React.ReactElement { Finishing sign-in… + {tookTooLong ? ( + + + This is taking longer than expected. + + router.replace(`/onboarding`)} + /> + + ) : null} ) } @@ -191,5 +216,17 @@ function createStyles(tokens: Tokens) { lineHeight: lineHeight.base, textAlign: `center`, }, + escape: { + alignSelf: `stretch`, + alignItems: `center`, + gap: spacing.sm, + marginTop: spacing.lg, + }, + subtext: { + color: tokens.text3, + fontSize: fontSize.sm, + lineHeight: lineHeight.sm, + textAlign: `center`, + }, }) } From 1cfe0c690a9c8f0f7abf26cc1a02b4cdfa3a7a91 Mon Sep 17 00:00:00 2001 From: msfstef Date: Thu, 18 Jun 2026 16:53:20 +0300 Subject: [PATCH 3/4] Clean up agents-mobile auth logging and legal link - Gate the cloud-auth console.warn calls behind __DEV__ via an exported devWarn helper (reused from CloudAuthContext) so token-exchange / HTTP-status details don't get written to production device logs. - Point the account-deletion link at electric.ax to match the terms/privacy URLs (it was on a different domain). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/lib/CloudAuthContext.tsx | 3 ++- packages/agents-mobile/src/lib/cloudAuth.ts | 20 ++++++++++++------- .../src/screens/AccountScreen.tsx | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/agents-mobile/src/lib/CloudAuthContext.tsx b/packages/agents-mobile/src/lib/CloudAuthContext.tsx index 200067359e..1aae5e5a62 100644 --- a/packages/agents-mobile/src/lib/CloudAuthContext.tsx +++ b/packages/agents-mobile/src/lib/CloudAuthContext.tsx @@ -2,6 +2,7 @@ import { createContext, useContext, useEffect, useMemo, useState } from 'react' import * as Linking from 'expo-linking' import { cloudAuth, + devWarn, getCloudBaseUrl, isCallbackUrl, type CloudAuthProvider, @@ -68,7 +69,7 @@ export function CloudAuthProvider({ } }) .catch((err) => { - console.warn(`[agents-mobile] cloud-auth getInitialURL failed:`, err) + devWarn(`[agents-mobile] cloud-auth getInitialURL failed:`, err) }) return () => subscription.remove() }, []) diff --git a/packages/agents-mobile/src/lib/cloudAuth.ts b/packages/agents-mobile/src/lib/cloudAuth.ts index 73674baeed..7999dea269 100644 --- a/packages/agents-mobile/src/lib/cloudAuth.ts +++ b/packages/agents-mobile/src/lib/cloudAuth.ts @@ -24,6 +24,12 @@ export { getCloudServiceIdFromServerUrl } from './cloudAgentUrls' * param the client sent. */ +// Auth diagnostics help in development but shouldn't write token-exchange +// / HTTP-status details to production device logs (logcat / Console.app). +export const devWarn = (...args: Array): void => { + if (__DEV__) console.warn(...args) +} + export type CloudAuthProvider = `github` | `google` export type CloudAuthStatus = @@ -577,11 +583,11 @@ export class CloudAuth { body: `{}`, }) } catch (err) { - console.warn(`[agents-mobile] cloud-auth: getTokenForAgents fetch:`, err) + devWarn(`[agents-mobile] cloud-auth: getTokenForAgents fetch:`, err) return null } if (!res.ok) { - console.warn( + devWarn( `[agents-mobile] cloud-auth: getTokenForAgents returned ${res.status}` ) return null @@ -617,25 +623,25 @@ export class CloudAuth { body: `{"json":{}}`, }) } catch (err) { - console.warn(`[agents-mobile] cloud-auth: whoami fetch failed:`, err) + devWarn(`[agents-mobile] cloud-auth: whoami fetch failed:`, err) return } if (res.status === 401 || res.status === 403) { - console.warn( + devWarn( `[agents-mobile] cloud-auth: whoami returned ${res.status}; signing out` ) await this.signOut() return } if (!res.ok) { - console.warn(`[agents-mobile] cloud-auth: whoami returned ${res.status}`) + devWarn(`[agents-mobile] cloud-auth: whoami returned ${res.status}`) return } let body: unknown try { body = await res.json() } catch (err) { - console.warn(`[agents-mobile] cloud-auth: whoami body parse:`, err) + devWarn(`[agents-mobile] cloud-auth: whoami body parse:`, err) return } const result = parseWhoamiUserResponse(body) @@ -709,7 +715,7 @@ export class CloudAuth { try { listener(next) } catch (err) { - console.warn(`[agents-mobile] cloud-auth listener threw:`, err) + devWarn(`[agents-mobile] cloud-auth listener threw:`, err) } } } diff --git a/packages/agents-mobile/src/screens/AccountScreen.tsx b/packages/agents-mobile/src/screens/AccountScreen.tsx index 53df9c6390..b27059aeb2 100644 --- a/packages/agents-mobile/src/screens/AccountScreen.tsx +++ b/packages/agents-mobile/src/screens/AccountScreen.tsx @@ -9,7 +9,7 @@ import { useCloudAuth } from '../lib/CloudAuthContext' import { fontSize, lineHeight, radii, spacing } from '../lib/theme' import type { Tokens } from '../lib/theme' -const DELETE_ACCOUNT_URL = `https://electric-sql.com/about/legal/delete-account` +const DELETE_ACCOUNT_URL = `https://electric.ax/about/legal/delete-account` /** * Settings → Account screen. Mirrors the desktop's `AccountPage` — From acaecff69a7cc6abee61ad9d3e537be1c9ffae3d Mon Sep 17 00:00:00 2001 From: msfstef Date: Thu, 18 Jun 2026 17:30:01 +0300 Subject: [PATCH 4/4] Stop blocking WRITE_EXTERNAL_STORAGE (breaks Android <=9 camera) Independent review of the permission changes found that blocking WRITE_EXTERNAL_STORAGE breaks photo capture on Android 7-9: expo-image-picker 17's launchCameraAsync -> ensureCameraPermissionsAreGranted() hard-requires that permission to be GRANTED on API < 29 (ImagePickerModule.kt), and a blocked (tools:node="remove") permission can never be granted. Library picking and Android 10+ are unaffected. Keep blocking RECORD_AUDIO + READ_EXTERNAL_STORAGE (genuinely unused); leave WRITE_EXTERNAL_STORAGE in place. It's inert on Android 10+ and a benign legacy permission, not a Play review risk. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/agents-mobile/app.config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/agents-mobile/app.config.ts b/packages/agents-mobile/app.config.ts index 57a7bf68e7..5a2ba9ac5a 100644 --- a/packages/agents-mobile/app.config.ts +++ b/packages/agents-mobile/app.config.ts @@ -130,12 +130,14 @@ export default ({ config }: ConfigContext): ExpoConfig => versionCode, edgeToEdgeEnabled: true, // expo-image-picker declares these, but the app uses the system - // photo picker (content URIs) and never records audio — block them - // so the AAB permission list / Play Data Safety form stays clean. + // photo picker (content URIs) and never records audio, so block + // them to keep the AAB permission list clean. WRITE_EXTERNAL_STORAGE + // is deliberately left in place: image-picker's pre-Android-10 + // (API < 29) camera path hard-requires it, so blocking it breaks + // "take a photo" on Android 7–9. blockedPermissions: [ `android.permission.RECORD_AUDIO`, `android.permission.READ_EXTERNAL_STORAGE`, - `android.permission.WRITE_EXTERNAL_STORAGE`, ], adaptiveIcon: { foregroundImage: `./assets/adaptive-icon.png`,