chore(release): v0.4.13 (versionCode 40) — waitlist retry queue (#166)
* chore(release): v0.4.13 (versionCode 40) — waitlist retry queue reaches users
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 <noreply@paperclip.ing>
* fix(waitlist): stamp the app version into the mailto escape hatch
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<version>" (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 <noreply@paperclip.ing>
---------
Co-authored-by: engineer <engineer@macbookpro.lan>
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -94,8 +94,8 @@ android {
|
|||||||
applicationId 'cc.agentlabs.opencode'
|
applicationId 'cc.agentlabs.opencode'
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 39
|
versionCode 40
|
||||||
versionName "0.4.12"
|
versionName "0.4.13"
|
||||||
|
|
||||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||||
}
|
}
|
||||||
|
|||||||
4
app.json
4
app.json
@@ -2,7 +2,7 @@
|
|||||||
"expo": {
|
"expo": {
|
||||||
"name": "OpenCode",
|
"name": "OpenCode",
|
||||||
"slug": "opencode-mobile",
|
"slug": "opencode-mobile",
|
||||||
"version": "0.4.12",
|
"version": "0.4.13",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"scheme": "opencode",
|
"scheme": "opencode",
|
||||||
"userInterfaceStyle": "automatic",
|
"userInterfaceStyle": "automatic",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"package": "cc.agentlabs.opencode",
|
"package": "cc.agentlabs.opencode",
|
||||||
"versionCode": 39,
|
"versionCode": 40,
|
||||||
"usesCleartextTraffic": true,
|
"usesCleartextTraffic": true,
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import { buildAuth } from "../../src/lib/auth"
|
|||||||
import { AnalyticsEvent, track } from "../../src/lib/analytics"
|
import { AnalyticsEvent, track } from "../../src/lib/analytics"
|
||||||
import { submitWaitlistSignup, buildWaitlistMailtoUrl, needsManualEscapeHatch, type QueuedSignup } from "../../src/lib/waitlist"
|
import { submitWaitlistSignup, buildWaitlistMailtoUrl, needsManualEscapeHatch, type QueuedSignup } from "../../src/lib/waitlist"
|
||||||
import { flushPendingSignups, queuePendingSignup, readPendingSignups, dropPendingSignup } from "../../src/lib/waitlist-queue-storage"
|
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() {
|
export default function AddConnectionScreen() {
|
||||||
const colorScheme = useColorScheme()
|
const colorScheme = useColorScheme()
|
||||||
@@ -281,7 +286,7 @@ export default function AddConnectionScreen() {
|
|||||||
// Last-resort, user-initiated only. Never opened automatically.
|
// Last-resort, user-initiated only. Never opened automatically.
|
||||||
const openWaitlistMailto = async (email: string) => {
|
const openWaitlistMailto = async (email: string) => {
|
||||||
try {
|
try {
|
||||||
await Linking.openURL(buildWaitlistMailtoUrl(email))
|
await Linking.openURL(buildWaitlistMailtoUrl(email, APP_VERSION))
|
||||||
} catch {
|
} catch {
|
||||||
Alert.alert(t("connection.add.waitlist.alertTitle"), t("connection.add.waitlist.noMailAppMessage"))
|
Alert.alert(t("connection.add.waitlist.alertTitle"), t("connection.add.waitlist.noMailAppMessage"))
|
||||||
}
|
}
|
||||||
|
|||||||
5
distribution/changelogs/40.txt
Normal file
5
distribution/changelogs/40.txt
Normal file
@@ -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.
|
||||||
@@ -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.
|
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.
|
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<version>`
|
||||||
|
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
|
## Method / reproducing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
5
fastlane/metadata/android/en-US/changelogs/40.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/40.txt
Normal file
@@ -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.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@opencode-ai/mobile",
|
"name": "@opencode-ai/mobile",
|
||||||
"version": "0.4.12",
|
"version": "0.4.13",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -49,6 +49,21 @@ test("mailto escape hatch works without an email", () => {
|
|||||||
assert.ok(!url.includes("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 ---
|
// --- retry decision ---
|
||||||
|
|
||||||
// 502 named explicitly: the server returns it when Brevo itself fails, and the
|
// 502 named explicitly: the server returns it when Brevo itself fails, and the
|
||||||
|
|||||||
@@ -36,10 +36,20 @@ export function buildWaitlistPayload(email: string): { email: string; source: st
|
|||||||
* automatically: a failed signup is queued locally and retried
|
* automatically: a failed signup is queued locally and retried
|
||||||
* (waitlist-queue.ts). This URL is only offered after retries keep failing,
|
* (waitlist-queue.ts). This URL is only offered after retries keep failing,
|
||||||
* behind an explicit "still not working? email us" tap.
|
* 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 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}`
|
return `mailto:${WAITLIST_FALLBACK_EMAIL}?subject=${subject}&body=${body}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user