Files
opencode-mobile/distribution/waitlist-signup-path-coverage.md
Den af7da46c0f 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>
2026-08-14 02:02:53 -07:00

77 lines
4.7 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.
# Waitlist signup-path coverage — how much of the install base can't sign up in-app
_Measured 2026-08-14 for [AGE-61]. Re-run with `node scripts/play-version-share.mjs --github`._
## The question
The in-app OpenCode Connect waitlist form posts to `POST /api/beta-signup` (Brevo list 4).
That code path first shipped in **v0.4.8** (commit `0fdfb54`, 2026-07-18). Every older build
has exactly one path: open a `mailto:` to support@agentlabs.cc, which lands in a human inbox
and nowhere near the waitlist store. 20 of 21 signups between 2026-08-03 and 2026-08-13 were
lost that way. So: **how many people are still on a build with no working signup path?**
## Answer, by channel
| Channel | Population measured | Pre-v0.4.8 share |
| --- | --- | --- |
| Google Play (auto-updates) | ~90–100 daily distinct users, 2026-07-31 → 2026-08-13 | **0%** — Play reports a single versionCode, `142` (v0.4.10). Any older build is below Play's reporting floor (<10 users/day). |
| Sideload — GitHub release APKs (never auto-update) | 1,682 lifetime APK downloads across all releases | **25.9%** (436 downloads) are pre-v0.4.8 builds. |
There is no iOS App Store listing (`itunes.apple.com/lookup?bundleId=cc.agentlabs.opencode`
returns 0 results) and the app is not on IzzyOnDroid, so those channels contribute nothing.
## What that means
1. **Play is not the problem.** Auto-update did its job: the measurable Android active base is
effectively 100% on a build that can sign up through the API.
2. **The stale cohort is sideload-only and permanent.** ~436 devices pulled an APK that predates
the signup API. Nothing will ever update them; they will keep emitting mailto signups until
the owner manually re-downloads. The hourly reconciler
(`VibeBrowserProductPage/.github/workflows/waitlist-mailto-reconcile.yml`) is what keeps those
signups from being lost, and it is not a temporary measure.
3. **Stale builds are not the only source of mailto signups** — they are, as of AGE-87, the only
*remaining* one. Until v0.4.12 the fallback also fired on network error, timeout (8s) and 5xx,
so a user on a current build with flaky mobile data took the same lossy path. That path is gone:
a failed signup is now persisted on-device (`opencode.waitlist.pending.v1`, AsyncStorage) and
retried on every app foreground (`src/lib/waitlist.ts` queue section, flushed from
`app/_layout.tsx`). `mailto:` is only ever opened by an explicit user tap after repeated
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<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
```bash
GOOGLE_PLAY_SERVICE_ACCOUNT_JSON="$(cat play-store-key.json)" \
node scripts/play-version-share.mjs --days 14 --github
```
- Play numbers come from the Play Developer Reporting API, `crashRateMetricSet` →
`distinctUsers` grouped by `versionCode`. That is Play's own vitals denominator: users who
actually opened the app. It only covers devices with usage-and-diagnostics sharing on, and Play
rounds it — which is why we quote a share, never an absolute install count.
- Play versionCodes are **not** the ones in `android/app/build.gradle`: the publish workflow
overwrites them with `github.run_number + 100`. The mapping in the script was derived from the
successful runs of `publish-play-store.yml` (`versionCode 139` = v0.4.8 = first build with the
API path).
- The service account is the same `PLAY_STORE_SERVICE_ACCOUNT_JSON` already used to publish; the
script needs only the `playdeveloperreporting` scope and mints its own token, no extra deps.
[AGE-61]: https://github.com/dzianisv/VibeBrowserProductPage/pull/234