fix(ios): close final release review blockers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Dennis V
2026-07-14 17:49:53 +00:00
parent c287ecad70
commit d1071b2a44
10 changed files with 50 additions and 16 deletions

View File

@@ -7,7 +7,9 @@
# ── HUMAN GATE (one-time, after Apple Developer Program enrollment) ──────────────
# Complete ALL of the following before releasing. Do NOT invent any of these IDs.
#
# 1. Fill and commit the eas.json placeholders (see eas.json.README.md for click paths):
# 1. Link the app to an Expo project and add its UUID as the GitHub Actions
# repository variable EAS_PROJECT_ID (see eas.json.README.md), then fill and
# commit the eas.json placeholders:
# submit.production.ios.ascAppId REPLACE_WITH_APP_STORE_CONNECT_APP_ID → numeric App Store Connect App ID
# submit.production.ios.appleTeamId REPLACE_WITH_APPLE_TEAM_ID → 10-char Apple Team ID
#
@@ -26,7 +28,8 @@
# SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT in Expo before releasing.
#
# Build number is managed remotely by EAS (eas.json: cli.appVersionSource=remote,
# build.production.ios.autoIncrement=buildNumber). No app.json mutation happens here.
# build.production.ios.autoIncrement=buildNumber). The workflow only injects the
# EAS project linkage into its temporary runner copy of app.json.
name: Publish to App Store (TestFlight)
@@ -54,6 +57,7 @@ jobs:
env:
EAS_CLI_VERSION: "21.0.0"
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
EAS_PROJECT_ID: ${{ vars.EAS_PROJECT_ID }}
steps:
- uses: actions/checkout@v6
@@ -80,6 +84,13 @@ jobs:
need "APPLE_APP_STORE_CONNECT_API_KEY_ID" "${ASC_KEY_ID:-}"
need "APPLE_APP_STORE_CONNECT_ISSUER_ID" "${ASC_ISSUER_ID:-}"
need "APPLE_APP_STORE_CONNECT_API_KEY" "${ASC_KEY_B64:-}"
if [ -z "${EAS_PROJECT_ID:-}" ]; then
echo "::error::Missing required repository variable: EAS_PROJECT_ID"
fail=1
elif ! printf '%s' "$EAS_PROJECT_ID" | grep -Eq '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'; then
echo "::error::EAS_PROJECT_ID must be an Expo project UUID"
fail=1
fi
asc_app_id=$(node -p "require('./eas.json').submit.production.ios.ascAppId || ''")
team_id=$(node -p "require('./eas.json').submit.production.ios.appleTeamId || ''")
case "$asc_app_id" in
@@ -113,6 +124,18 @@ jobs:
- name: Install dependencies (deterministic)
run: npm ci --legacy-peer-deps
- name: Configure EAS project linkage
run: |
set -euo pipefail
node -e "
const fs = require('fs');
const j = require('./app.json');
j.expo.extra = { ...j.expo.extra, eas: { ...j.expo.extra?.eas, projectId: process.env.EAS_PROJECT_ID } };
fs.writeFileSync('app.json', JSON.stringify(j, null, 2) + '\n');
"
test "$(node -p "require('./app.json').expo.extra.eas.projectId")" = "$EAS_PROJECT_ID"
echo "Linked build to Expo project $EAS_PROJECT_ID."
- name: Configure App Store Connect API key
env:
ASC_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}

View File

@@ -21,6 +21,7 @@ import {
} from "../../src/lib/notifications"
import type { Category } from "../../src/lib/notifications"
import { hasTelemetryConsent, setTelemetryConsent } from "../../src/lib/telemetry"
import { PRIVACY_POLICY_URL } from "../../src/lib/links"
function SettingRow({
icon,
@@ -215,7 +216,7 @@ export default function SettingsScreen() {
label="Privacy Policy"
description="What data we collect and how"
isDark={isDark}
onPress={() => Linking.openURL("https://agentlabs.cc/opencode/privacy")}
onPress={() => Linking.openURL(PRIVACY_POLICY_URL)}
right={<Ionicons name="open-outline" size={20} color={isDark ? "#666666" : "#999999"} />}
/>
</SettingSection>

View File

@@ -141,13 +141,15 @@ While waiting for Apple's verification call and approval:
- Note: Key ID and Issuer ID
- Base64-encode the .p8 and store in GitHub secret `APPLE_APP_STORE_CONNECT_API_KEY`
4. Configure the EAS `production` environment for optional crash reporting:
4. Run `eas init` once, then add the generated `extra.eas.projectId` UUID as the GitHub Actions repository variable `EAS_PROJECT_ID`.
5. Configure the EAS `production` environment for optional crash reporting:
- `EXPO_PUBLIC_SENTRY_DSN`
- `SENTRY_AUTH_TOKEN` (secret visibility)
- `SENTRY_ORG`
- `SENTRY_PROJECT`
5. Create an internal TestFlight group and add yourself as tester
6. Create an internal TestFlight group and add yourself as tester
---

View File

@@ -90,7 +90,7 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
| 9 | Export compliance | ✅ Done | `ITSAppUsesNonExemptEncryption: false` added to `app.json`. Answers + rationale in this doc (see Export Compliance section above) and `distribution/app-store-listing.md`. |
| 10 | ATS justification in App Review notes | ✅ Done | Full justification text in `distribution/app-store-listing.md` under "App Review Notes — ATS Justification" |
| 11 | Reviewer test instructions | ✅ Done | Updated with correct command (`opencode serve --hostname 0.0.0.0`) in `distribution/app-store-listing.md` |
| 12 | GitHub secrets: `EXPO_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — see `.github/workflows/publish-app-store.yml` header |
| 12 | GitHub variable `EAS_PROJECT_ID`; secrets: `EXPO_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — run `eas init`, then see `.github/workflows/publish-app-store.yml` |
| 13 | Update `eas.json` placeholders: `ascAppId` + `appleTeamId` | User | 🟡 post-enrollment — see `eas.json.README.md` for click paths |
| 14 | CI workflow validated | CI | 🟡 Linux checks pass; PR must prove the macOS Simulator build |
| 15 | TestFlight release notes | ✅ Done | `distribution/whatsnew-ios/release-notes-en-US.txt` — polished, 1658 chars (limit 4000) |
@@ -102,7 +102,7 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
1. (manual) Sign in to App Store Connect, create app with bundle id `cc.agentlabs.opencode`.
2. (manual) Generate App Store Connect API key (App Manager role) → download `.p8` → base64 encode → add as GitHub secret.
3. (manual) Update `eas.json` placeholders (Team ID, ASC App ID).
4. (manual) `eas login` + `eas build:configure` for first-time setup (managed signing).
4. (manual) `eas login` + `eas init` + `eas build:configure`; add the generated project UUID as repository variable `EAS_PROJECT_ID`.
5. (automated) Publish a GitHub Release for the version tag → CI calls EAS Build → EAS Submit → IPA lands in TestFlight.
6. (manual, first time) Add internal testers in App Store Connect → distribute via TestFlight.
7. (manual) After internal testing OK → submit for App Store review (production).

View File

@@ -152,7 +152,7 @@ When the opencode AI agent requests a file-access permission, the notification b
**Files:** `app.json:29`, `app.json:38-39`
**Description:** Both platforms allow HTTP connections, which is required for local/LAN servers. This is intentional and correct for the use case. However, neither the Play Store listing, App Store listing, nor a privacy policy document currently explains that HTTP connections may be made to user-provided servers. Google Play's Data Safety section and Apple's App Privacy report will flag arbitrary network access if not documented.
**Remediation:**
1. Update the privacy policy at `agentlabs.cc/opencode/privacy` to explain that the app connects to user-configured server addresses that may use HTTP.
1. Update the canonical privacy policy at `https://dzianisv.github.io/opencode-mobile/privacy/` to explain that the app connects to user-configured server addresses that may use HTTP.
2. In Play Store Data Safety: disclose "Other app performance data" collected (crash reports via Sentry — opt-in).
**Status:** Open

View File

@@ -73,10 +73,12 @@ Alternatively, in App Store Connect:
## After filling in the placeholders
1. Commit the updated `eas.json` to the repo.
2. Add the `EXPO_TOKEN` and App Store Connect API GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` for the exact list).
3. Publish a GitHub Release for the version tag (or manually dispatch the App Store workflow).
4. The release event triggers CI to build the IPA via EAS and submit that exact build to TestFlight.
1. Run `eas init` once to create/link the Expo project.
2. Copy the generated `extra.eas.projectId` UUID and add it as the GitHub Actions repository variable `EAS_PROJECT_ID`. The release workflow injects it into `app.json` only on the runner.
3. Commit the updated `eas.json` to the repo.
4. Add the `EXPO_TOKEN` and App Store Connect API GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` for the exact list).
5. Publish a GitHub Release for the version tag (or manually dispatch the App Store workflow).
6. The release event triggers CI to build the IPA via EAS and submit that exact build to TestFlight.
---

View File

@@ -7,6 +7,7 @@
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Modal, Linking } from "react-native"
import { Ionicons } from "@expo/vector-icons"
import { PRIVACY_POLICY_URL } from "../lib/links"
interface Props {
visible: boolean
@@ -45,9 +46,7 @@ export function TelemetryConsentModal({ visible, onAllow, onDecline }: Props) {
</View>
{/* Privacy policy link */}
<TouchableOpacity
onPress={() => Linking.openURL("https://agentlabs.cc/opencode/privacy")}
>
<TouchableOpacity onPress={() => Linking.openURL(PRIVACY_POLICY_URL)}>
<Text style={styles.privacyLink}>Read our full privacy policy</Text>
</TouchableOpacity>

1
src/lib/links.ts Normal file
View File

@@ -0,0 +1 @@
export const PRIVACY_POLICY_URL = "https://dzianisv.github.io/opencode-mobile/privacy/"

View File

@@ -232,7 +232,10 @@ export function createClient(config: ClientConfig) {
try {
yield JSON.parse(data)
} catch (err) {
console.warn("[SSE] Failed to parse event:", data.slice(0, 200), err)
console.warn("[SSE] Failed to parse event", {
length: data.length,
error: err instanceof Error ? err.message : String(err),
})
}
}
}

View File

@@ -50,6 +50,8 @@ export function initSentry() {
return scrubEvent(event)
},
beforeBreadcrumb(crumb) {
// Console output can contain malformed server payloads, prompts, or code.
if (crumb.category === "console") return null
if (crumb.data && typeof crumb.data === "object") {
crumb.data = redactObject(crumb.data as Record<string, unknown>)
}
@@ -149,6 +151,7 @@ function scrubEvent<T extends Sentry.Event>(event: T): T {
}
}
if (event.breadcrumbs) {
event.breadcrumbs = event.breadcrumbs.filter((crumb) => crumb.category !== "console")
for (const crumb of event.breadcrumbs) {
if (typeof crumb.message === "string") crumb.message = redactString(crumb.message)
if (crumb.data && typeof crumb.data === "object") {