Tags: electric-sql/electric
Tags
agents-mobile: store-readiness polish (manifests, permissions, icons,… … splash) (#4626) ## Overview Audit + polish pass on **`packages/agents-mobile`** focused on the **app binary's** readiness for **App Store + Google Play review** (store-listing content like screenshots/descriptions is out of scope). The goal was low-hanging fruit and chores — missing manifests, icons, permissions hygiene, cold-start polish — that matter for passing review. Done as a 4-agent investigation (2 reading the code, 2 researching current 2025–2026 Apple/Google requirements from official + community sources), with the high-stakes claims verified first-hand against the code and an Android prebuild. This PR description is the **reference record** of that investigation so future sessions on this branch don't repeat the research. App context: Expo SDK 54, RN 0.81.5, New Architecture, expo-router, managed (CNG) workflow, `com.electricsql.agents.mobile`, iOS min target 16.4. OAuth sign-in (GitHub/Google via system browser), AsyncStorage, Sentry, photo attachments (expo-image-picker), WebView/DOM chat timeline. --- ## What this PR changes Three commits: **1. Harden store config: privacy manifest, permissions, splash, icons** (`app.config.ts`, assets, deps) - **iOS Privacy Manifest** (`ios.privacyManifests`) declaring the required-reason APIs used by AsyncStorage + Sentry (`CA92.1`, `C617.1`, `35F9.1`, `E174.1`), `NSPrivacyTracking: false`, and crash/perf data types (not linked, not tracking). Apple **auto-rejects uploads (ITMS-91053)** that call these without a declaration; statically-linked pods' own manifests aren't reliably read, so we declare app-level. - **`microphonePermission: false`** on expo-image-picker + Android `blockedPermissions` for `RECORD_AUDIO` and `READ_EXTERNAL_STORAGE` (verified stripped via `tools:node="remove"`). The app uses the system photo picker (content URIs, no broad media access) and never records audio, so those only inflate the Play permission list. `WRITE_EXTERNAL_STORAGE` is intentionally **not** blocked — expo-image-picker's pre-Android-10 (API < 29) camera path hard-requires it, so blocking it breaks photo capture on Android 7–9 (caught in the independent permissions review). - **Splash screen** — added `expo-splash-screen` + `assets/splash-icon.png` (dark `#101217`, dark variant). There was none, so cold start flashed blank white against the dark theme. - **Opaque iOS icon** (flattened the transparent corners onto `#101217`; Apple rejects icons with alpha) + **monochrome adaptive-icon layer** for Android 13+ themed icons. - **`expo-system-ui`** so `userInterfaceStyle: automatic` actually applies on Android (prebuild warned it was a no-op without it). - App `description`. **2. Add error boundary and OAuth callback timeout** (`app/_layout.tsx`, `app/oauth/callback.tsx`) - `Sentry.ErrorBoundary` around the provider tree (themed "Try again" fallback) so an uncaught render error is recoverable instead of a blank/native crash a reviewer can hit. Sentry still reports the error. - 10s escape hatch on `/oauth/callback` so a cold start with no pending request can't spin on "Finishing sign-in…" forever — surfaces "Back to sign-in". **3. Clean up auth logging and legal link** (`cloudAuth.ts`, `CloudAuthContext.tsx`, `AccountScreen.tsx`) - Gate cloud-auth `console.warn` calls behind `__DEV__` (exported `devWarn` helper, reused from the context) so token-exchange / HTTP-status details don't hit production device logs. - Point the account-deletion link at `electric.ax` to match the terms/privacy URLs (it was on a different domain — `electric-sql.com`). --- ##⚠️ NOT done in this PR — needs product/backend decisions These are real review blockers but can't be config-only fixes: 1. **In-app account deletion** (Apple 5.1.1(v) + Google Play Data deletion). `AccountScreen` currently only opens a web page whose own copy says "your account is not deleted by tapping the button … email support to start the request" — the canonical *rejected* pattern. Needs a genuine in-app deletion flow calling a backend delete endpoint (the same admin-API `cloudAuth` already uses) with a confirm dialog + local sign-out. A web link is fine as the *secondary* path, not the only one. - Reviewer trap: a reviewer may delete the demo account, breaking later reviews — special-case it and document testing steps in review notes. 2. **Sign in with Apple** (Apple 4.8). iOS offers **only** Google + GitHub social login (`OnboardingScreen.tsx`). 4.8 generally requires an equivalent privacy-preserving login (SIWA) when social logins are the mechanism. Needs `expo-apple-authentication` + backend support for Apple as an identity provider, or a defensible "client for a specific service" argument. iOS-only; Android can stay Google/GitHub. **Decision needed.** 3. **AI content report/flag** (Apple 1.2 UGC + Google Generative-AI policy). An AI-chat app must let users flag/report generated content in-app. **Verify whether `agents-server-ui` already provides this**; if not it's a likely blocker on both stores (may also need block-user + a visible support contact). --- ## Should-fix (mostly submission-process / out-of-binary) - **Demo account + reachable server URL in review notes.** Login-gated app = the #1 "couldn't sign in" rejection. The reviewer can't pass onboarding without a working Electric Cloud account *and* a reachable agents server. Reviews run from US/China IPs — ensure the demo backend is reachable, disable 2FA (or document the bypass), and note that sign-in opens an external browser so it doesn't look broken. - **Chat WebView has no error/offline/retry state** (`app/session.tsx` + `agents-server-ui` `EmbedApp.tsx`). The DOM embed shows "Connecting…/Loading session…" indefinitely on connection failure; server-down only surfaces as a red dot in the overflow menu. A reviewer on a flaky network sees a permanent spinner = "broken." **Deliberately not fixed here:** the embed ref exposes no connection/error signal, and a speculative native health-poll overlay on this flash-sensitive screen (see the Expo DOM embed flash issue) risks false "can't connect" flashes that read worse than a spinner. Proper fix: expose connection state from the embed ref, or add an error+retry state inside the shared `EmbedApp.tsx` (affects desktop/web too). - **Verify the built AAB** after an EAS production build: `bundletool dump config --bundle=app.aab` shows `PAGE_ALIGNMENT_16K`, and the merged manifest has no stray `AD_ID` / `QUERY_ALL_PACKAGES` (reconcile with Data Safety / Advertising ID declaration if present). - Permission purpose strings were tightened to be specific (Apple rejects generic ones). Confirm the camera path (`launchCameraAsync`) is actually used; if photo-library-only ships, drop `cameraPermission`. ## Polish / nice-to-have - A11y quick wins: `accessibilityRole="link"` on the legal Terms/Privacy `<Text onPress>`; decorative `Icon` SVGs not hidden from screen readers; no Dynamic Type / `allowFontScaling` (fixed px sizes). - Cold-start theme flash: `ThemeProvider` defaults to dark, so a light-mode device may briefly flash dark before hydrating. Splash mitigates; could seed the initial scheme from `Appearance.getColorScheme()` synchronously. - `resolveVersionCode()` dev fallback (`Date.now()/1000`) is a latent footgun if a local build ever reaches a store upload (Play caps versionCode at 2.1e9). CI always writes `.build-info.json` so low risk; consider throwing on store profiles when neither env nor build-info is present. --- ## Already satisfied by the stack (verified — no action) - **Target API 35+** (Play min since 2025-08-31 / ext. 2025-11-01) — SDK 54 targets API 36. - **16 KB page size** (Play, since 2025-11-01) — RN 0.81 compliant; verify on the built AAB. - **Edge-to-edge** (forced on API 36) — `edgeToEdgeEnabled: true` + `react-native-safe-area-context` insets handled across screens. - **AAB output** — `production`/`canary-store` use `distribution: "store"`; keep the `apk` preview/canary profiles out of `eas submit`. - **Play App Signing**, **no foreground service** — fine. - **Encryption export compliance** — `ITSAppUsesNonExemptEncryption: false` is correct (HTTPS/OAuth/AsyncStorage). **No ATT** needed (Sentry isn't tracking, no IDFA) — don't add `expo-tracking-transparency`. - **iOS 26 SDK / Xcode 26** required for uploads since 2026-04-28 — build on EAS's current image (build target ≠ deployment target; min 16.4 stays). - **New age-rating questionnaire** (since 2026-01-31) — answer in App Store Connect. - **Native debug symbols** Play warning is advisory (Sentry symbolicates separately) — safe to ignore. --- ## Verification `expo-doctor` 18/18 · `tsc --noEmit` clean · `eslint` clean · `vitest` 88/88 · `expo prebuild --platform android` confirmed the blocked permissions (`tools:node="remove"`), the `<monochrome>` adaptive layer, and the `splashscreen_logo` resources (incl. `drawable-night-*`). `pnpm install --frozen-lockfile` passes on the trimmed lockfile. > Generated with the help of multi-agent research; key dated sources include Apple's privacy-manifest enforcement (2024-05-01), Guideline 4.8 revision (2024-01-25), AI-consent rule 5.1.2(i) (2025-11-13), iOS 26 SDK requirement (2026-04-28); Google's target API 35 (2025-08-31) and 16 KB page size (2025-11-01) deadlines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PreviousNext