From f6499dc64f1888572d6f93828908e055706f22f6 Mon Sep 17 00:00:00 2001 From: engineer Date: Fri, 14 Aug 2026 01:33:27 -0700 Subject: [PATCH 1/2] =?UTF-8?q?chore(release):=20v0.4.13=20(versionCode=20?= =?UTF-8?q?40)=20=E2=80=94=20waitlist=20retry=20queue=20reaches=20users?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ships 2f81d34 (#165): failed waitlist signups are persisted on-device and retried on app foreground instead of silently falling back to mailto. Until this Play release, no user is running that fix. Co-Authored-By: Paperclip --- android/app/build.gradle | 4 ++-- app.json | 4 ++-- distribution/changelogs/40.txt | 5 +++++ distribution/whatsnew/whatsnew-en-US | 4 ++-- fastlane/metadata/android/en-US/changelogs/40.txt | 5 +++++ package.json | 2 +- 6 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 distribution/changelogs/40.txt create mode 100644 fastlane/metadata/android/en-US/changelogs/40.txt diff --git a/android/app/build.gradle b/android/app/build.gradle index dd5c4ab3..8cc13842 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -94,8 +94,8 @@ android { applicationId 'cc.agentlabs.opencode' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 39 - versionName "0.4.12" + versionCode 40 + versionName "0.4.13" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } diff --git a/app.json b/app.json index 1df38db3..ca26cfdf 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "OpenCode", "slug": "opencode-mobile", - "version": "0.4.12", + "version": "0.4.13", "orientation": "portrait", "scheme": "opencode", "userInterfaceStyle": "automatic", @@ -35,7 +35,7 @@ }, "android": { "package": "cc.agentlabs.opencode", - "versionCode": 39, + "versionCode": 40, "usesCleartextTraffic": true, "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", diff --git a/distribution/changelogs/40.txt b/distribution/changelogs/40.txt new file mode 100644 index 00000000..d82c7f45 --- /dev/null +++ b/distribution/changelogs/40.txt @@ -0,0 +1,5 @@ +v0.4.13 — Waitlist signups no longer get lost + +• Joining the OpenCode Connect waitlist now retries in the background if the network drops. +• A pending signup is saved on-device and re-sent the next time you open the app. +• The email-app fallback only opens if you tap it after repeated failures. diff --git a/distribution/whatsnew/whatsnew-en-US b/distribution/whatsnew/whatsnew-en-US index 93587afb..45a2f005 100644 --- a/distribution/whatsnew/whatsnew-en-US +++ b/distribution/whatsnew/whatsnew-en-US @@ -1,3 +1,3 @@ -v0.4.12 — Global recent sessions +v0.4.13 — Waitlist signups no longer get lost -Recent Sessions now shows every session across all your projects automatically — no need to pick a folder or directory first. The list is populated globally as soon as you connect, and falls back to per-directory listing on older servers. +Joining the OpenCode Connect waitlist now survives a flaky connection: a signup that fails is saved on your device and re-sent automatically the next time you open the app. The email-app fallback only opens if you choose it after repeated retries. diff --git a/fastlane/metadata/android/en-US/changelogs/40.txt b/fastlane/metadata/android/en-US/changelogs/40.txt new file mode 100644 index 00000000..22744ecb --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40.txt @@ -0,0 +1,5 @@ +v0.4.13 — waitlist signups no longer get lost + +- Joining the OpenCode Connect waitlist now retries in the background if the network drops. +- A pending signup is saved on-device and re-sent the next time you open the app. +- The email-app fallback only opens if you tap it after repeated failures. diff --git a/package.json b/package.json index 458e53d6..e2236949 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/mobile", - "version": "0.4.12", + "version": "0.4.13", "private": true, "main": "expo-router/entry", "scripts": { From c7c1b4ba5588c3cff3cf686a0eff18c6054283c7 Mon Sep 17 00:00:00 2001 From: engineer Date: Fri, 14 Aug 2026 01:36:21 -0700 Subject: [PATCH 2/2] fix(waitlist): stamp the app version into the mailto escape hatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AGE-100 asks for the post-release mailto count "split by app version where the mail body allows it". It did not allow it: the body was "Sign me up!\n\nEmail: x" and nothing else, so a mail from an unreachable pre-v0.4.8 sideload is byte-identical to one from a current build whose retry queue leaked. Those two readings have opposite meanings — the first is the known permanent cohort, the second is a defect. Now the escape hatch appends "App: OpenCode Mobile v" (app.json, same source Sentry uses). Absence of the line == pre-v0.4.13 build. waitlist.ts stays free of react-native/JSON imports; the screen injects the version. Co-Authored-By: Paperclip --- app/connection/add.tsx | 7 ++++++- distribution/waitlist-signup-path-coverage.md | 17 +++++++++++++++++ src/lib/waitlist.test.ts | 15 +++++++++++++++ src/lib/waitlist.ts | 14 ++++++++++++-- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/app/connection/add.tsx b/app/connection/add.tsx index dc7fe23b..b8616086 100644 --- a/app/connection/add.tsx +++ b/app/connection/add.tsx @@ -23,6 +23,11 @@ import { buildAuth } from "../../src/lib/auth" import { AnalyticsEvent, track } from "../../src/lib/analytics" import { submitWaitlistSignup, buildWaitlistMailtoUrl, needsManualEscapeHatch, type QueuedSignup } from "../../src/lib/waitlist" import { flushPendingSignups, queuePendingSignup, readPendingSignups, dropPendingSignup } from "../../src/lib/waitlist-queue-storage" +import appJson from "../../app.json" + +// Same read as sentry.ts: app.json is the single source of the user-visible +// version (package.json/gradle are kept in parity by `npm run check:versions`). +const APP_VERSION = (appJson as { expo?: { version?: string } }).expo?.version ?? "unknown" export default function AddConnectionScreen() { const colorScheme = useColorScheme() @@ -281,7 +286,7 @@ export default function AddConnectionScreen() { // Last-resort, user-initiated only. Never opened automatically. const openWaitlistMailto = async (email: string) => { try { - await Linking.openURL(buildWaitlistMailtoUrl(email)) + await Linking.openURL(buildWaitlistMailtoUrl(email, APP_VERSION)) } catch { Alert.alert(t("connection.add.waitlist.alertTitle"), t("connection.add.waitlist.noMailAppMessage")) } diff --git a/distribution/waitlist-signup-path-coverage.md b/distribution/waitlist-signup-path-coverage.md index 33b132c0..10c8f61b 100644 --- a/distribution/waitlist-signup-path-coverage.md +++ b/distribution/waitlist-signup-path-coverage.md @@ -38,6 +38,23 @@ returns 0 results) and the app is not on IzzyOnDroid, so those channels contribu retry failures. Expect the reconciler's `synced_count` to trend toward the sideload cohort only. Any plan that assumes "ship an update and the leak closes" is still wrong for those ~436 devices. +## After-number: how it gets attributed (AGE-100) + +_Pending — v0.4.13 (the first build users can actually run the retry queue on) ships 2026-08-14._ + +Baseline at release time, from the hourly reconciler's last run before the cut +(`Waitlist Mailto Reconcile`, 2026-08-14 07:59 UTC): `scanned=34 synced=0 skipped=34 failed=0` +— i.e. 34 known mailto conversations, all already healed into the store, nothing new that hour. + +The reconciler reads a Chatwoot mail body, so "split by app version" only works if the body +carries one. It did not. As of v0.4.13 the escape hatch stamps `App: OpenCode Mobile v` +into the mail (`buildWaitlistMailtoUrl`, `src/lib/waitlist.ts`). That gives a clean read a week out: + +| Mail body | Means | +| --- | --- | +| no `App:` line | a build older than v0.4.13 — expected, this is the ~436-device sideload cohort no release can reach | +| `App: OpenCode Mobile v0.4.13` or newer | a current build still reached mailto — the retry queue leaked, **file it as a new defect with the mail as evidence** | + ## Method / reproducing ```bash diff --git a/src/lib/waitlist.test.ts b/src/lib/waitlist.test.ts index 62219803..a42a70e2 100644 --- a/src/lib/waitlist.test.ts +++ b/src/lib/waitlist.test.ts @@ -49,6 +49,21 @@ test("mailto escape hatch works without an email", () => { assert.ok(!url.includes("Email")) }) +// AGE-100: the reconciler has to be able to say which build produced a mailto +// signup. Pre-v0.4.8 sideloads (no signup API, unreachable by any release) +// send no App: line; a current build sending one means the retry queue leaked +// and that is a new defect, not the known stale cohort. +test("mailto escape hatch stamps the app version when given one", () => { + const url = buildWaitlistMailtoUrl("dev@example.com", "0.4.13") + assert.ok(url.includes(encodeURIComponent("App: OpenCode Mobile v0.4.13"))) + assert.ok(url.includes(encodeURIComponent("Email: dev@example.com"))) +}) + +test("mailto escape hatch omits the version line when the version is unknown", () => { + const url = buildWaitlistMailtoUrl("dev@example.com") + assert.ok(!url.includes("App%3A")) +}) + // --- retry decision --- // 502 named explicitly: the server returns it when Brevo itself fails, and the diff --git a/src/lib/waitlist.ts b/src/lib/waitlist.ts index ac66f0f3..11f9cb00 100644 --- a/src/lib/waitlist.ts +++ b/src/lib/waitlist.ts @@ -36,10 +36,20 @@ export function buildWaitlistPayload(email: string): { email: string; source: st * automatically: a failed signup is queued locally and retried * (waitlist-queue.ts). This URL is only offered after retries keep failing, * behind an explicit "still not working? email us" tap. + * + * `appVersion` stamps the build into the mail body (AGE-100). Without it the + * hourly reconciler that heals these mails + * (VibeBrowserProductPage/scripts/reconcile-waitlist-mailto.js) cannot tell a + * ~436-device pre-v0.4.8 sideload — which has no signup API at all and can + * never be fixed by shipping code — from a current build that still fell back, + * which would be a live defect. Absence of the line means "pre-v0.4.13 build". */ -export function buildWaitlistMailtoUrl(email: string): string { +export function buildWaitlistMailtoUrl(email: string, appVersion?: string): string { const subject = encodeURIComponent("OpenCode Connect Waitlist") - const body = encodeURIComponent(email ? `Sign me up!\n\nEmail: ${email}` : "Sign me up!") + const lines = ["Sign me up!"] + if (email) lines.push("", `Email: ${email}`) + if (appVersion) lines.push("", `App: OpenCode Mobile v${appVersion}`) + const body = encodeURIComponent(lines.join("\n")) return `mailto:${WAITLIST_FALLBACK_EMAIL}?subject=${subject}&body=${body}` }