feat(mobile): Android and web support (staging)

Draft
KI
in 23m
+15021-2594

How this branch works

android/staging collects the work as cherry-picked commits from the android workspaces (all landed). The last three commits are the release tail: chore(mobile): Android release config and two fingerprint: commits (Android label + R8 rule, Expo web dependencies). Supersedes #9118.

What

apps/mobile runs on iOS, Android and web from one codebase. Each platform gets its most native UI; plain React Native is the fallback. iOS behaviour is unchanged.

  • Rule (now in apps/mobile/AGENTS.md): use an @expo/ui universal component or community drop-in first (SwiftUI on iOS, Jetpack Compose on Android, DOM on web); otherwise X.ios.tsx / X.android.tsx / X.web.tsx behind an X.types.ts contract; X.tsx is a React Native fallback that must work.
  • Android native: Google sign-in via Credential Manager, agent card as an ongoing notification (Kotlin), Material menus (MenuView) for share access, file actions, composer mode and symbol buttons, Compose AlertDialog for the rename prompt, Material bottom sheet for attachments, header actions through a toolbarIcon seam.
  • Web: Expo web target (react-native-web). Header buttons and menus through a HeaderToolbar seam (36 screens), chat with token streaming and bottom pinning, diffs, Pages in an iframe, voice orb fallback, browser session cookie, terminal in an iframe, browser file chooser, browser WebRTC, backdrop-filter glass, in-app alert dialog, keyboard focus rings, reload-to-update gate. API accepts origins in MOBILE_WEB_ORIGINS (unset = none).
  • Web at /agents: the web build is served at app.superset.sh/agents, same-origin with the dashboard (shared sign-in cookie, no extra CORS). apps/web rewrites /agents/* in its proxy so the route keeps its own CSP; the sidebar and Home link to it, and /workspaces/[id] offers "Continue in browser". Off until set up: needs a superset-agents Vercel project, VERCEL_AGENTS_PROJECT_ID and AGENTS_URL (production) secrets — steps in docs/agents-web.md. Without them the links stay hidden and deploy-agents skips.
  • Web on a computer: tab titles per screen; Escape closes sheets; sheets open as a dialog (desktop) or bottom sheet (narrow) over the screen they came from; Enter sends and Shift+Enter breaks the line; click-to-type terminal; search focuses on open and Return opens the top hit; trackpad scrolling in diffs; hover states; rows are real links (⌘-click / middle-click / copy link); workspace, chat and composer keep to a centred 720px column on wide screens; back after a deep link goes to the parent screen (all platforms).
  • Android: choices (Branch, review comment Edit/Delete) open in a Compose bottom sheet; search sheet has a close button; empty Home no longer scrolls.
  • Android composer: dictation (SpeechRecognizer; Web Speech on web), paste of images and files, PDF thumbnails, swipe-down; voice mode owns the Android audio route; update gate points to Google Play.
  • All platforms: sent file attachments open in the platform viewer (QuickLook on iOS, ACTION_VIEW on Android, new tab on web).
  • Shared fallback: the React Native composer is Composer.tsx (Android, web, tests); iOS keeps the SwiftUI composer.
  • Dev setup and store: scripts/android-dev.sh, .agents/skills/mobile-android-verification, play-store.config.js, scripts/play-listing.ts, plans/20261029-android-launch-checklist.md, manual deploy-android.yml workflow.

⚠️ The last three commits start an iOS release

They change the iOS runtime fingerprint. Merging them makes deploy.yml build and submit a new iOS binary, and installed iOS builds stop taking OTA updates until they update. Drop all three to merge the Android and web code dark (web then needs its dependency commit back before it builds); every commit before them leaves the fingerprint unchanged.

Verified

  • Tests: mobile 277, api 240, shared 1504, auth 88, all pass. Biome clean. tsc: mobile 188 (pre-existing), api/shared 0. check:i18n: 0 missing in 16 locales. sherif clean.
  • expo export for iOS, Android and web all succeed. iOS fingerprint unchanged by everything after the release tail.
  • iOS simulator, merged staging (review account, production): Home, workspace, native composer, attachments sheet, workspace "…" menu identical.
  • Android emulator, merged staging: composer "+" Material sheet, system photo picker; signed-in header pass on the local stack (Home, workspace menu, files changed, jump to file, sessions, PR overflow and link, pages, version gate) all render and work.
  • Web: production export boots; per-workspace verification on the local stack covers headers, chat streaming and scroll, Pages, attachments, uploads, alerts, focus rings, version gate, CORS with MOBILE_WEB_ORIGINS.

Not verified / not done

  • Push notifications (needs a Firebase project and an APNs key). A real Google/GitHub login round trip on Android (needs a Play-signed install); App Links wait for the Play signing key.
  • Recognised speech on the Android emulator (the recogniser path ran, the emulator mic heard nothing); the voice sheet's mute toolbar (no live voice session).
  • Found, pre-existing on every platform, left for separate PRs against main: native chat shows a streaming reply only when the turn ends (LegendList memo); the checks sheet's fix button was never wired; PR sheets drop explicit owner/repo; the voice failure alert shows raw OpenAI JSON.

and light.

Not verified / not done

  • A real GitHub or Google login round trip on Android (native Google needs a Play-signed install). App Links wait for the Play signing key.
  • Web: Stack.Toolbar header controls do not render on web (~40 screens); a hosted origin needs adding to the API CORS list and better-auth trusted origins; chat sessions, the voice orb (Rive) and the Pages webview are unverified on web.
  • Dictation and voice on Android, push notifications on both platforms, PR checks sheets on Android (no entry point yet).

Summary by cubic

Adds Android and web support to apps/mobile; iOS behavior is unchanged. Every iOS-only API now sits behind a typed platform contract picked by Metro, so Android and web resolve to React Native fallbacks instead of @expo/ui, native Expo modules, and Sign in with Apple.

Android

  • React Native composer (shared fallback for web and tests) with mic dictation, audio-route ownership, clipboard paste, PDF thumbnails, and swipe-down; plus session tabs, quick keys, slash suggestions, a sheet header, glass surface, alert-prompt, and forward-swipe implementations.
  • Native Google sign-in via Credential Manager; no Apple button on Android; social sign-in failures surface their error.
  • Agent card as an ongoing notification with a stale "Not updating" form and per-row session navigation, plus a one-time notification permission request and a Chinese Intl fix for Hermes.
  • Native Material surfaces: MenuView menus, a Compose AlertDialog rename prompt, a bottom-sheet attachments picker, and a Compose host theme.
  • android-dev.sh dev setup (one emulator per worktree, no stale Metro port reuse), a verification skill, Play listing config, and App Links intent filters.
  • Screen fixes: toolbar icons as vector drawables, a JS search field, sheet headers that never show route names, aligned page comment pins, Send-to-chat on the diff screen, top-aligned multiline fields, and an empty Home that no longer scrolls.
  • Host-service requests get a 60 s deadline so Android cannot hang; Android keeps its query cache in a file; the update gate names Google Play. The home-screen label comes from the brand name and is stripped from the runtime fingerprint; an R8 keep rule preserves WorkManager's merger classes.

Web

  • Header buttons and menus render through a HeaderToolbar seam; chat markdown renders and code highlights via Shiki's JS engine; the chat list pins to the newest item only while the reader is at the bottom, streaming token by token.
  • The voice orb falls back to an animated pulse; pages and the terminal open in iframes; attachments use the browser file chooser; voice uses the browser's WebRTC; Alert.alert draws in a modal with all its buttons; the browser's session cookie carries auth; glass uses backdrop-filter. Sent attachments open in the platform viewer — QuickLook, an Android intent, or a new tab.
  • Tabs take the screen's title and Escape closes a sheet; the composer sends on Enter, breaks the line on Shift+Enter, and closes on Escape. Upgrade and manage-plan buttons name the web host they open.
  • The app exports under /agents, which apps/web serves on the dashboard origin, sharing its session cookie and its own CSP; CI deploys the export to its own Vercel project. /agents stays dark until AGENTS_URL and that project exist.

Before merging

  • The last three commits (Android release config, home-screen-label/fingerprint, Expo web dependencies) change the iOS runtime fingerprint: merging them builds and submits a new iOS binary, and installed iOS builds stop taking OTA updates until they update. Drop them to merge the Android work dark.