Skip to content

Commit 9a9d918

Browse files
msfstefclaude
andcommitted
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) <noreply@anthropic.com>
1 parent ac2391d commit 9a9d918

7 files changed

Lines changed: 124 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@electric-ax/agents-mobile': patch
3+
---
4+
5+
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.

packages/agents-mobile/app.config.ts

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export default ({ config }: ConfigContext): ExpoConfig =>
2020
slug: `agents-mobile`,
2121
owner: `electric-ax`,
2222
scheme: `electric-agents`,
23+
description: `Mobile client for Electric Agents — connect to your agents servers and chat with running agents.`,
2324
version: packageJson.version,
2425
runtimeVersion: packageJson.version,
2526
orientation: `portrait`,
@@ -29,6 +30,21 @@ export default ({ config }: ConfigContext): ExpoConfig =>
2930
plugins: [
3031
`expo-router`,
3132
`expo-web-browser`,
33+
// Branded launch screen so cold start shows the logo on the
34+
// app's dark background instead of a blank white flash.
35+
[
36+
`expo-splash-screen`,
37+
{
38+
backgroundColor: `#101217`,
39+
image: `./assets/splash-icon.png`,
40+
imageWidth: 200,
41+
resizeMode: `contain`,
42+
dark: {
43+
backgroundColor: `#101217`,
44+
image: `./assets/splash-icon.png`,
45+
},
46+
},
47+
],
3248
// The chat WebView (Expo DOM / streamdown) ships regex lookbehind,
3349
// which JavaScriptCore only parses on iOS 16.4+. Below that the whole
3450
// DOM bundle fails to parse and the chat renders blank.
@@ -45,8 +61,11 @@ export default ({ config }: ConfigContext): ExpoConfig =>
4561
[
4662
`expo-image-picker`,
4763
{
48-
photosPermission: `Allow Electric Agents to attach photos to messages.`,
49-
cameraPermission: `Allow Electric Agents to take a photo to attach to a message.`,
64+
photosPermission: `Electric Agents accesses your photo library so you can attach an existing photo to a chat message.`,
65+
cameraPermission: `Electric Agents uses your camera so you can take a photo to attach to a chat message.`,
66+
// The app never records audio; `false` drops the RECORD_AUDIO
67+
// (Android) + NSMicrophoneUsageDescription (iOS) the plugin adds.
68+
microphonePermission: false,
5069
},
5170
],
5271
],
@@ -59,14 +78,70 @@ export default ({ config }: ConfigContext): ExpoConfig =>
5978
ITSAppUsesNonExemptEncryption: false,
6079
},
6180
supportsTablet: true,
81+
// Apple rejects uploads (ITMS-91053) that call "required reason"
82+
// APIs without declaring them, and doesn't reliably read
83+
// statically-linked pods' own manifests — so declare app-level.
84+
// UserDefaults + file timestamp come from AsyncStorage / RN core;
85+
// system boot time + disk space from Sentry.
86+
privacyManifests: {
87+
NSPrivacyTracking: false,
88+
NSPrivacyTrackingDomains: [],
89+
NSPrivacyCollectedDataTypes: [
90+
{
91+
NSPrivacyCollectedDataType: `NSPrivacyCollectedDataTypeCrashData`,
92+
NSPrivacyCollectedDataTypeLinked: false,
93+
NSPrivacyCollectedDataTypeTracking: false,
94+
NSPrivacyCollectedDataTypePurposes: [
95+
`NSPrivacyCollectedDataTypePurposeAppFunctionality`,
96+
],
97+
},
98+
{
99+
NSPrivacyCollectedDataType: `NSPrivacyCollectedDataTypePerformanceData`,
100+
NSPrivacyCollectedDataTypeLinked: false,
101+
NSPrivacyCollectedDataTypeTracking: false,
102+
NSPrivacyCollectedDataTypePurposes: [
103+
`NSPrivacyCollectedDataTypePurposeAppFunctionality`,
104+
],
105+
},
106+
],
107+
NSPrivacyAccessedAPITypes: [
108+
{
109+
NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryUserDefaults`,
110+
NSPrivacyAccessedAPITypeReasons: [`CA92.1`],
111+
},
112+
{
113+
NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryFileTimestamp`,
114+
NSPrivacyAccessedAPITypeReasons: [`C617.1`],
115+
},
116+
{
117+
NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategorySystemBootTime`,
118+
NSPrivacyAccessedAPITypeReasons: [`35F9.1`],
119+
},
120+
{
121+
NSPrivacyAccessedAPIType: `NSPrivacyAccessedAPICategoryDiskSpace`,
122+
NSPrivacyAccessedAPITypeReasons: [`E174.1`],
123+
},
124+
],
125+
},
62126
},
63127
android: {
64128
...config.android,
65129
package: applicationId,
66130
versionCode,
67131
edgeToEdgeEnabled: true,
132+
// expo-image-picker declares these, but the app uses the system
133+
// photo picker (content URIs) and never records audio — block them
134+
// so the AAB permission list / Play Data Safety form stays clean.
135+
blockedPermissions: [
136+
`android.permission.RECORD_AUDIO`,
137+
`android.permission.READ_EXTERNAL_STORAGE`,
138+
`android.permission.WRITE_EXTERNAL_STORAGE`,
139+
],
68140
adaptiveIcon: {
69141
foregroundImage: `./assets/adaptive-icon.png`,
142+
// Android 13+ themed (monochrome) icons — white silhouette
143+
// the launcher tints to match the user's wallpaper.
144+
monochromeImage: `./assets/adaptive-icon-monochrome.png`,
70145
backgroundColor: `#101217`,
71146
},
72147
},
10.2 KB
Loading
-7.39 KB
Loading
10 KB
Loading

packages/agents-mobile/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
"expo-image-picker": "~17.0.11",
3434
"expo-linking": "~8.0.12",
3535
"expo-router": "~6.0.24",
36+
"expo-splash-screen": "~31.0.13",
3637
"expo-status-bar": "~3.0.9",
38+
"expo-system-ui": "~6.0.9",
3739
"expo-web-browser": "~15.0.11",
3840
"react": "19.1.0",
3941
"react-dom": "19.1.0",

pnpm-lock.yaml

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)