fix(onboarding): clarify opencode-serve requirement + fail connect fast (retention) (#107)

* fix(onboarding): clarify opencode-serve requirement and fail connect tests fast

New users bounce at ~0% 7-day retention because nothing tells them the app
needs a computer running `opencode serve` on the same network/Tailscale, and
a bad IP hangs for the full 30s request timeout before failing.

- Rewrite the no-connection empty state subtitle and add a "How to set up a
  server" link to the setup guide (app/(tabs)/index.tsx, src/lib/links.ts).
- Surface the opencode-serve prerequisite as a one-line notice at the top of
  the Quick Connect form, above the existing detailed help box
  (app/connection/add.tsx).
- Give the interactive connection test (testConnection) its own 12s timeout
  via an optional Client.global.health(timeoutMs) parameter, instead of
  reusing the general 30s REQUEST_TIMEOUT_MS used for real session traffic
  (src/lib/sdk.ts, src/stores/connections.ts).
- Mirror all new/changed strings in the zh-Hans catalog; catalog-parity test
  keeps them in sync.

* docs(distribution): add retention analysis motivating first-run fixes

Diagnoses ~0% D7 retention as product-shape (no path to value without a
self-hosted server, no demo mode, store copy sets no expectation). Ranks
fixes and isolates the two owner-only strategic calls (store-copy honesty,
hosted OpenCode Connect).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6

* fix(onboarding): drop connect-screen prerequisite notice (kept off-screen the submit button in E2E)

The added notice pushed connect-submit-button below the fold, breaking the
Maestro activation-positive flow (and the other flows sharing the connect
prelude). The empty state already sets the opencode-serve expectation one
screen earlier, so this notice was redundant. Empty-state guidance + guide
link and the fast-fail connect timeout are unaffected and retained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6

---------

Co-authored-by: engineer <engineer@macbookpro.lan>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Den
2026-07-17 17:55:12 -07:00
committed by GitHub
parent 2285f80e81
commit d54a74d3f5
7 changed files with 125 additions and 12 deletions

View File

@@ -0,0 +1,72 @@
# Retention Analysis — opencode-mobile
_Last updated: 2026-07-17_
## The number that matters
- **Installs: 1K+** (Google Play, live listing, package `cc.agentlabs.opencode`).
- **7-day retention: ~0%** (per Notion "Retention Investigation").
Awareness is **not** the bottleneck anymore. Getting people to install works. Getting
them to *stay* does not. Every hour spent on more distribution channels is spent on the
wrong problem until activation is fixed.
## Diagnosis: it's product-shape, not a bug
A brand-new installer's path (traced in code):
1. Launch → **telemetry consent modal first** (`app/_layout.tsx:157-168`) — a privacy
prompt before the user has seen any value.
2. Land on Sessions tab, empty state: "No Connection / Add a server connection to get
started" (`app/(tabs)/index.tsx:421-440`, `src/lib/i18n/en.json:258-259`). No hint
that this needs a *separate computer*.
3. Tap Add → Quick Connect form asking for **IP address + port + password**
(`app/connection/add.tsx:225-407`), hand-typed on a phone keyboard.
4. To get past this, the user must **already** have, on another machine: Node/npm,
`opencode-ai` installed, `opencode serve` running, and LAN or Tailscale reachability.
5. A wrong IP hangs the spinner up to **30s** before any feedback
(`REQUEST_TIMEOUT_MS=30_000`, `src/lib/sdk.ts:175`).
**There is no demo, sample, or try-it mode anywhere in the app.** For any installer
without a self-hosted server already running — almost certainly the majority of casual
Play installs — the app is functionally inert after onboarding: empty state → a form
they can't fill → a "coming soon" mailing-list card (`app/connection/add.tsx:343-397`).
Nothing else to tap.
The store listing makes it worse: the short description
("AI coding agent in your pocket. Stream, diff, approve — free & open source.")
sets **zero** expectation that a self-hosted server is required. People install expecting
a standalone app, hit a wall, and uninstall. That is the ~0% D7 mechanism.
## Fixes, ranked by impact ÷ effort
| # | Change | Impact | Effort | Owner-gated? |
|---|--------|--------|--------|--------------|
| 1 | Set the self-hosting expectation in the empty state + connect screen; fail-fast timeout | High | Low | No — shipping via PR |
| 2 | One-screen "what you need" pre-flight with an "I don't have a server yet" branch | High | Med | No |
| 3 | QR-code pairing (server prints QR of URL+creds; app scans) — kills manual IP/port/password typos | High | Med | No |
| 4 | Local demo/sandbox session — scripted walkthrough of chat/diff/approve, no server needed | High | Med-High | No |
| 5 | **Store-copy honesty**: move the server prerequisite into the short description + first screenshot | High (on D7) | Low | **Yes — Play Console** |
| 6 | Hosted "OpenCode Connect" so users need no server at all (the real fix) | Highest | High | **Yes — strategic/infra** |
Items 1–4 are agent-shippable. Item 1 is in flight (branch
`fix/first-run-onboarding-clarity`).
## The one decision only the owner can make
**Do we optimize for install count or for activated users?**
- Item 5 (put "needs a self-hosted server" in the store's short description) will
**reduce installs** — it filters out people who can never activate. It will **raise**
retention and store rating quality. This is a positioning tradeoff, not an engineering
one, so it is the owner's call, not an agent's. Recommended: yes, be honest up front —
a 4-star app with 300 real users beats a 2-star app with 1,000 bouncing ones.
- Item 6 (hosted OpenCode Connect) is the only change that removes the server prerequisite
entirely. The waitlist card already in the app signals intent. This is the actual
long-term retention fix and deserves a roadmap decision.
## What this loop is doing about it
- Shipping items 1 (and scoping 2) as reviewable PRs.
- Not touching the live Play listing (item 5) or infra (item 6) — surfacing them here for
the owner instead of acting unilaterally on positioning/strategy.