Files
opencode-mobile/distribution/fdroid-submission/SIZE-OPTIMIZATION.md
Den 2b9b571d6e feat(privacy+dist): telemetry consent gate + app store distribution prep (#4)
* fix(security): fail closed on biometric init error

H-03: setting isAuthenticated: true on initialization failure was a
security bypass — any crash during biometric setup granted full access.
Fail closed instead; user sees auth prompt on next open.

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

* fix(security): use Crypto.randomUUID for connection IDs

H-04: Math.random() is not cryptographically random. Connection IDs are
used as SecureStore key suffixes; switch to expo-crypto randomUUID for
a secure source.

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

* fix(deps): pin expo-crypto to ~15.0.9

15.0.10 does not exist on npm; ~15.0.9 is the latest stable in the 15.x series compatible with Expo SDK 54.

* feat: add OpenCode Connect coming-soon waitlist card

Adds a discoverable 'OpenCode Connect — Coming Soon' card to the
add-connection quick-connect screen. Users can enter their email and
tap 'Join Waitlist' to send a pre-filled mailto. No backend required.

* fix(cua): detect actual screen dimensions and fix JSON parsing

- Get real screen size via `wm size` instead of hardcoding 1080x2400;
  emulator is 1080x1920 so y-coordinates were systematically off
- Extract first JSON object via regex when model returns multiple objects
- Use AZURE_OPENAI_MODEL env var for deployment name (defaults gpt-5.4)
- Add AZURE_DEV_AI_* path for Azure AI Foundry endpoints

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

* fix(security): SHA-pin upload-google-play and sanitize notification bodies

M-02: Pin r0adkll/upload-google-play to commit SHA e738b9d (v1.1.5)
to prevent supply-chain hijack via tag mutation.

M-03: Sanitize all push notification bodies — strip control chars,
truncate to 200 chars. Prevents server-supplied strings (error messages,
file paths from permission patterns, session titles) from leaking
unbounded text into the OS notification drawer.

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

* feat(privacy): add telemetry consent gate for Sentry crash reporting

Sentry was always-on, violating F-Droid anti-feature policy and user
trust norms. Now gated behind explicit opt-in:

- First-launch consent modal (TelemetryConsentModal) shows once on
  fresh install; user can Allow or Decline.
- Consent state persisted in expo-secure-store (survives restarts).
- Settings > Privacy section: crash reporting toggle + privacy policy link.
- initSentry() called only after consent granted — not on app start.

Closes #3 (partial)

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

* feat(config): add real icons and complete iOS/Android app.json config

- Add 1024×1024 app icon, 432×432 adaptive icon foreground, 200×200 splash
- iOS: push notification entitlement (aps-environment: production), speech/
  microphone/camera/photo usage descriptions for future features, disable
  ITSAppUsesNonExemptEncryption
- Android: adaptive icon with dark background (#0F172A), versionCode: 1
- expo-notifications plugin wired in app.json

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

* feat(dist): add iOS CI workflow, README rewrite, CONTRIBUTING, and LICENSE

- publish-app-store.yml: EAS Build + TestFlight submission; runs on tag/release/
  workflow_dispatch; bumps ios.buildNumber from github.run_number
- README: full rewrite — features, install badges, connection guide, contributing
- CONTRIBUTING.md: contribution guide for OSS contributors
- LICENSE: MIT

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

* docs(dist): add store listings, strategy, privacy policy, F-Droid/IzzyOnDroid templates

- distribution/strategy.md: monetization strategy (free client + opencode Cloud)
- distribution/play-listing.md: Google Play store copy (name, description, tags)
- distribution/app-store-listing.md: App Store listing copy
- distribution/privacy-policy.{md,html}: GDPR-compliant privacy policy
- distribution/PLAY_CONSOLE_SETUP.md: Play Console setup runbook
- distribution/ios-enrollment-runbook.md: Apple Developer Program enrollment steps
- distribution/SIGNING-KEY-FINGERPRINTS.md: keystore fingerprint for reproducible builds
- distribution/fdroid-submission/: F-Droid metadata template
- distribution/izzyondroid-submission/: IzzyOnDroid submission template
- distribution/whatsnew/: Play Store release notes (en-US)
- distribution/whatsnew-ios/: TestFlight release notes
- distribution/play-graphics/: Play Store screenshot placeholders
- distribution/app-store-graphics/: App Store screenshot placeholders

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

* fix(telemetry): handle SecureStore failure + Android back button

- add .catch() on loadTelemetryConsent() so SecureStore rejection
  shows the consent modal instead of blocking startup forever
- add onRequestClose={onDecline} to Modal so Android back button
  records the decline rather than silently dismissing
- fix catch block in telemetry.ts to not clobber _resolved when
  SecureStore read fails mid-session

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

* fix(ci): run gradlew clean to prevent stale modules.json duplicate

Sentry Gradle plugin writes modules.json to src/main/assets; cached
build intermediates contain an old copy → mergeReleaseAssets fails
with 'Duplicate resources'. Running clean before assembleRelease
clears the intermediate state.

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

* fix(ci): remove android build output cache causing duplicate modules.json

Caching android/app/build/intermediates and android/app/.cxx causes
two issues:
1. Stale modules.json in intermediates → Duplicate resources error
2. .cxx CMake artifacts reference absolute paths → ninja clean fails

Keeping only Gradle distribution cache (~/.gradle) which is safe.
Expo prebuild regenerates android sources fresh each run anyway.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:42:03 -07:00

164 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# APK Size Optimization for F-Droid / IzzyOnDroid
## Current state
| Artifact | Size |
|----------|------|
| Universal AAB (`app-release.aab`) | 58.5 MB |
| Per-ABI APK (estimated) | ~20–25 MB |
| IzzyOnDroid per-APK limit | 30 MB |
`bundletool` is not installed in this environment, so per-ABI APK sizes were not
measured directly. The estimate above is based on typical Expo/Hermes React Native
apps:
- Hermes JS engine binary: ~8–10 MB per ABI
- React Native native libraries: ~5–8 MB per ABI
- JavaScript bundle (ABI-independent): ~6–8 MB
- Assets (icons, splash, etc.): ~2–3 MB
**Conclusion:** arm64-v8a APK is likely ~20–25 MB — within IzzyOnDroid's 30 MB limit.
No ABI splits required for the initial submission.
To verify when bundletool is available:
```bash
bundletool build-apks \
--bundle=android/app/build/outputs/bundle/release/app-release.aab \
--output=apks.apks \
--mode=universal
# For per-ABI sizes:
bundletool build-apks \
--bundle=android/app/build/outputs/bundle/release/app-release.aab \
--output=apks-splits.apks \
--mode=default
unzip apks-splits.apks -d apk-splits/
ls -lh apk-splits/splits/
```
---
## If arm64-v8a APK exceeds 30 MB — fix approach
### Option A: ABI splits in Expo config plugin (recommended)
`android/app/build.gradle` is regenerated by `npx expo prebuild` on every CI run,
so direct edits are overwritten. The correct approach is an Expo config plugin.
Create `plugins/withAbiSplits.js`:
```js
const { withAppBuildGradle } = require('@expo/config-plugins');
module.exports = function withAbiSplits(config) {
return withAppBuildGradle(config, (config) => {
const contents = config.modResults.contents;
// Insert splits block inside android { ... } after defaultConfig
if (!contents.includes('splits {')) {
config.modResults.contents = contents.replace(
/defaultConfig \{/,
`splits {
abi {
reset()
enable true
universalApk true
include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
defaultConfig {`
);
}
return config;
});
};
```
Register in `app.json`:
```json
{
"expo": {
"plugins": [
"./plugins/withAbiSplits.js"
]
}
}
```
After `npx expo prebuild`, `android/app/build.gradle` will contain the splits block.
Run `./gradlew assembleRelease` — produces separate APKs per ABI.
For IzzyOnDroid / F-Droid: attach `app-arm64-v8a-release.apk` to the GitHub release.
### Option B: fdroid Gradle product flavor
Add a `fdroid` flavor that includes only arm64-v8a:
```groovy
// In the config plugin or directly in build.gradle (pre-prebuild override)
android {
flavorDimensions "distribution"
productFlavors {
fdroid {
dimension "distribution"
ndk {
abiFilters "arm64-v8a"
}
}
play {
dimension "distribution"
}
}
}
```
Build with: `./gradlew assembleFdroidRelease`
This also provides a clean hook for excluding FCM artifacts (see below).
---
## FCM / expo-notifications
`expo-notifications` compiles FCM receiver classes even when only local
notifications are used (the app only calls `scheduleNotificationAsync` — no
push token retrieval). F-Droid scanner may flag `com.google.firebase:firebase-messaging`
as `NonFreeNet` or `NonFreeDep`.
**Mitigation for F-Droid mainline:** add a `fdroid` product flavor that
excludes the FCM artifact:
```groovy
// In config plugin
android {
flavorDimensions "distribution"
productFlavors {
fdroid {
dimension "distribution"
}
}
}
configurations.fdroidImplementation {
exclude group: 'com.google.firebase', module: 'firebase-messaging'
}
```
Or patch `expo-notifications` `build.gradle` via a Gradle init script to
replace the FCM dependency with a no-op stub in the `fdroid` flavor.
**This is not required for IzzyOnDroid** (more tolerant of GMS dependencies
if they are not actively invoked). Track the F-Droid reviewer's feedback
before investing in this fix — they may accept without it given the
local-only usage.
---
## Action items (when ready to optimize)
1. Install bundletool: `sudo apt-get install bundletool` or download JAR from
https://github.com/google/bundletool/releases
2. Run `build-apks --mode=default` to measure per-ABI sizes
3. If arm64-v8a > 30 MB: implement config plugin (Option A above)
4. For F-Droid mainline FCM concern: implement `fdroid` product flavor (Option B)
5. Document measured sizes in this file once known