Skip to content

feat(mobile): Android support behind platform seams - #9118

Closed
Kitenite wants to merge 19 commits into
mainfrom
android-app-support-check-244611af
Closed

Kitenite wants to merge 19 commits into
mainfrom
android-app-support-check-244611af

Conversation

@Kitenite

@Kitenite Kitenite commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

What

Android support for apps/mobile, plus the setup to build, verify and ship it. iOS behaviour is unchanged.

  • Platform seams. Every iOS-only API (8 local Expo modules, @expo/ui/swift-ui, Sign in with Apple) sits behind a typed contract: X.types.ts + X.ios.tsx (native) + X.tsx/X.android.tsx. Metro picks the file per platform. Convention in apps/mobile/AGENTS.md.
  • Android implementations. React Native composer, sheet header and glass fallbacks, real fallbacks for alert-prompt / symbol-button / title-press / forward-swipe, native Google sign-in via Credential Manager (modules/google-credential; no Apple button on Android), agent card as an ongoing notification (Kotlin), Chinese Intl fix for Android Hermes.
  • Dev setup. apps/mobile/scripts/android-dev.sh + .agents/skills/mobile-android-verification.
  • Store. play-store.config.js, scripts/play-listing.ts, icon and feature graphic, and plans/20261029-android-launch-checklist.md.

⚠️ The last commit starts an iOS release

chore(mobile): Android release config changes app.config.ts, eas.json, apps/mobile/.gitignore and app-identity.ts, which feed the iOS runtime fingerprint (c1401836… → 965366c0…). Merging it makes deploy.yml build and submit a new iOS binary, and installed iOS builds stop taking OTA updates until they update. Drop that commit to merge the Android code dark; every commit before it leaves the fingerprint unchanged.

Verified

  • bun test (apps/mobile): 255 pass. Biome clean. tsc: no new errors (188 pre-existing, from an unbuilt @superset/host-service/router).
  • Metro bundles for iOS and Android: iOS resolves every .ios binding; the Android bundle has no @expo/ui/swift-ui or expo-apple-authentication.
  • On the Pixel 2 / API 33 emulator: boot, sign-in screen, Home, workspace, composer, sheets and the agent card were each checked by the step agents (screenshots in their reports). android-dev.sh ran end to end from a clean worktree.

Not verified

  • A real GitHub or Google login round trip on Android (needs the Google Cloud Android OAuth clients; in progress).
  • Several sheets (Page preview/comments/share, voice, PR checks, line comment).
  • Push notifications: planned in plans/20261029-android-agent-card.md, not implemented.

Summary by cubic

Adds Android support to apps/mobile with iOS behavior unchanged. All iOS-only APIs (@expo/ui/swift-ui, Sign in with Apple, local Expo modules) now sit behind typed contracts picked by Metro's platform extensions, and Android gets real implementations: a React Native composer, sheet header, glass fallbacks, alert prompts, forward-swipe, App Links, native Google sign-in via Credential Manager, the agent card as an ongoing notification, and a Chinese Intl fallback for Hermes. Also adds dev setup (android-dev.sh), a verification skill, Play Store listing, and a launch checklist.

⚠️ Merging this branch ships a new iOS build. The final commit, chore(mobile): Android release config, changes app.config.ts, eas.json, apps/mobile/.gitignore, and app-identity.ts, all of which feed the iOS runtime fingerprint. Merging it triggers deploy.yml to build and submit a new iOS binary, and installed iOS builds stop taking OTA updates until they update. Drop that commit to merge the Android code without an iOS release.

Written for commit 9a35339. Summary will update on new commits.

View guided diff Turn on auto-fix

Kitenite and others added 15 commits October 29, 2026 14:13
Each local Expo module and SwiftUI call site now has a typed contract
(X.types.ts), an iOS binding (X.ios.tsx) and a fallback (X.tsx) picked
by Metro platform extensions. Android bundles resolve to the fallbacks
and contain no @expo/ui/swift-ui or expo-apple-authentication code.
Native fingerprint is unchanged (only modules/*/ios is hashed).

Co-Authored-By: Claude <noreply@anthropic.com>
Composer.android.tsx implements the composer contract in React Native:
draft with initialDraft/onDraftChange, submit, isSending, canStop/onStop,
attachments tray, header chips, agent and launch-option pickers, the
permission-mode menu with SF Symbol names mapped to lucide icons,
onHeightChange, onExpandedChange and the ComposerHandle methods, plus
presentImage (a viewer hosted by the mounted composer) and fileThumbnail
(images only). iOS keeps its native binding unchanged.

Plan and the RN-vs-Kotlin decision: apps/mobile/plans/20261029-android-composer.md
- Serve /.well-known/assetlinks.json from apps/web beside the
  apple-app-site-association route, public in the proxy. The cert
  fingerprint list (ANDROID_SIGNING_CERT_SHA256) is empty until the
  upload and Play App Signing keys exist; it lives in constants.ts, not
  app-identity.ts, because app-identity.ts is hashed into the mobile
  runtime fingerprint.
- Add autoVerify https intent filters for /page/ and /workspaces/ on the
  same host as the iOS associated domain.
- eas.json: apk for development, development-simulator (inherited),
  preview and e2e-test; app-bundle for production; submit.production
  .android to the internal track as a draft, using the service account
  key stored on EAS.

EAS Metadata (store.config.js) supports only the App Store, so the Play
listing stays manual. eas-dev.sh stays iOS-only: `eas simulator --egress
local` supports only iOS. eas-runtime-version.test.ts and
fingerprint.config.js make no iOS-only assumption.

The iOS runtime fingerprint changes with this commit (eas.json, the
android block of the Expo config and app-identity.ts are all hashed for
every platform), so the next deploy builds and submits a new iOS binary.
… header seams

- alert-prompt: RN Modal + TextInput, drawn by AlertPromptHost at the root
- symbol-button: Pressable + lucide icon map, dropdown menu for items
- title-press: pressableTitle() gives a tappable headerTitle where the
  native bar cannot report taps; iOS returns undefined (native title)
- forward-swipe: right-edge pan strip (gesture-handler) that pushes
- GlassSurface and glassHeaderOptions seams: expo-glass-effect is now
  imported only from .ios files; Android gets an opaque header and a
  flat tonal surface
- WorkspaceScreen rename uses the alert-prompt seam, not Alert.prompt
- lingui scans apps/mobile/modules

Co-Authored-By: Claude <noreply@anthropic.com>
Ports the Live Activity contract to Android: one ongoing BigText
notification that asks to be promoted to a Live Update on Android 16+.
Same contract and caller; iOS unchanged (binding renamed .native.ts).
Plan covers the gaps and push token registration for both platforms.

Co-Authored-By: Claude <noreply@anthropic.com>
…droid app config

Android emulator boot spike (step 1 of Android support).

- intl-polyfills: Hermes on Android lists zh-Hans-CN/zh-Hant-TW but not
  zh-CN/zh-TW, so NumberFormat and DateTimeFormat formatted Chinese as
  en-US. scriptSubtagFallback.android.ts wraps both constructors and passes
  the maximized tag when the native one does not support the tag as
  written. iOS and bun resolve to a no-op. Measured on API 33 for all 17
  supported locales; nothing else throws or falls back.
- signInWithApple fallback (Android): surface the API error instead of
  failing silently. Apple web OAuth still fails server-side: the API has no
  Apple Services ID or client secret (CLIENT_ID_AND_SECRET_REQUIRED).
- app.config android: adaptive icon background #151110 to match the icon
  ground, and expo-localization supportedLocales for Android (generates
  locales_config.xml for per-app language).

Note: any app.config.ts edit changes the iOS runtime-version fingerprint.

Co-Authored-By: Claude <noreply@anthropic.com>
Android has no Sign in with Apple, so the provider list is per platform
(socialProviders.ios.ts keeps Apple; the .ts fallback drops it) and the
Apple web-OAuth fallback is gone. iOS renders and calls exactly what it
did: native Apple, browser GitHub and Google.

Google on Android goes through Credential Manager (the account bottom
sheet) via modules/google-credential, a small Android-only Kotlin Expo
module on androidx.credentials + googleid. It asks for an ID token for
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID (the API's GOOGLE_CLIENT_ID) with a
nonce and signs in with signIn.social({ provider: "google", idToken }).
better-auth already verifies that token against GOOGLE_CLIENT_ID, so the
server is unchanged. With no Google account on the device, or no client
ID configured, it falls back to the browser flow; a canceled sheet does
nothing.

Chosen over @react-native-google-signin/google-signin: its free edition
uses the deprecated legacy Google Sign-In SDK on Android (Credential
Manager is in the paid edition), and its config plugin and GoogleSignIn
pod would touch the iOS build. The local module has no config plugin,
so the iOS runtime fingerprint is unchanged (333eefdb...).

Co-Authored-By: Claude <noreply@anthropic.com>
…back fixes from device

- sheetHeaderOptions seam for formSheet routes: iOS is glassHeaderOptions
  unchanged; Android draws a JS drag handle + title (+ header items) and
  rounds the sheet top, since Android form sheets have no native bar
- contentScrollsUnderHeader seam: Home and the chat workspace keep a
  transparent header on iOS only; on Android the first rows sat behind
  the bar and the host/sort chips took no taps
- alert-prompt: KeyboardAvoidingView so the dialog stays above the IME
- Input: see-through selection colour on Android (white hid the text)
- symbol-button: style on the menu root, so the trigger fills its slot
- forward-swipe: 1px edge view with 40dp hit slop; taps under it reach
  the content and the swipe starts inside the gesture-nav Back zone

Verified on the Pixel 2 API 33 emulator.

Co-Authored-By: Claude <noreply@anthropic.com>
…poser

- collapsable={false} on the field's wrapper: flattening it in one state and
  not the other re-parented the field, and Android dropped focus.
- Lift by keyboard + bottom inset: RN reports the keyboard without the
  navigation bar.
- Blur on keyboardDidHide, so Back collapses the composer as on iOS.

Verified on the Pixel_2_API_33 emulator; findings in the plan.
Holds back the Android release config; see the final commit.
Holds back the Android release config; see the final commit.
Holds back the Android release config; see the final commit.
Holds back the Android release config; see the final commit.
Holds back the Android release config; see the final commit.
- scripts/android-dev.sh boots the emulator, builds the dev client only
  when the installed APK is not this worktree's (owner + fingerprint on
  the device), starts Metro with --clear, reverses the service ports and
  opens the app. --doctor, --reload, --user, --shot, --stop.
- .agents/skills/mobile-android-verification: Android mechanics and the
  traps found while porting (JDK 17, shared emulator, stale Metro cache,
  dev auto sign-in, iOS fingerprint files).
- plans/20261029-android-launch-checklist.md: account, signing, Google
  sign-in, App Links and Play steps a person must do, and known gaps.
- play-store.config.js + scripts/play-listing.ts: the Play listing
  (text reused from the App Store listing, icon, feature graphic, form
  answers) checked against Play's limits and pushed through the Play
  Developer API with --push.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@expo

expo Bot commented Oct 30, 2026

Copy link
Copy Markdown

No installed development build can load this pull request as an update: its native fingerprint is b24ee0de6b55 and no development build has it. Add the mobile-build label to build one.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

…uth clients

Co-Authored-By: Claude <noreply@anthropic.com>
@Kitenite
Kitenite force-pushed the android-app-support-check-244611af branch from a302b21 to fd722fd Compare October 30, 2026 01:53
…note

Co-Authored-By: Claude <noreply@anthropic.com>
Kitenite and others added 2 commits October 29, 2026 20:23
Parallel worktrees can each boot and drive their own AVD on a fixed port.

Co-Authored-By: Claude <noreply@anthropic.com>
…print)

app.config.ts (android block: adaptive icon, locales, App Links intent
filters), eas.json (Android build and submit profiles), the mobile
.gitignore and app-identity.ts all feed the iOS fingerprint. Merging
this commit to main makes deploy.yml build and submit a new iOS binary,
and installed iOS builds stop taking OTA updates until they update.
The assetlinks.json route and its constants come along because App
Links need the intent filters. Drop this commit to merge the Android
work without an iOS release.

Co-Authored-By: Claude <noreply@anthropic.com>
@Kitenite

Copy link
Copy Markdown
Member Author

Superseded by #9126, which carries this branch plus the follow-up Android work through android/staging.

@Kitenite Kitenite closed this Oct 30, 2026

This branch was successfully deployed

1 active deployment
preview — 9a35339e Deployed Oct 30, 2026 by Kitenite via Deploy Database (Neon) #13300
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant