From 3c88e16ae5beeefaaee009345f812f2051399373 Mon Sep 17 00:00:00 2001 From: Den <2119348+dzianisv@users.noreply.github.com> Date: Fri, 17 Jul 2026 21:47:13 -0700 Subject: [PATCH] fix(funnel): repair activation-path bugs found in end-to-end audit (#114) Systematic trace of the activation funnel (store listing -> docs setup guide -> in-app onboarding -> connect flow) after #113 showed a docs 404 bug had slipped through. Fixes found in this pass: - README.md and CONTRIBUTING.md still told developers to run `npm install -g opencode` (missing -ai), the exact 404 #113 fixed everywhere else. Same package-name fix applied here. - src/lib/diagnostics.ts: the health probe reported ok:true for any resolved fetch, including 401/403/404/500 responses. A wrong password or broken server therefore classified as "Health endpoint responded - connection actually works now.", contradicting the error shown right next to it. Health now requires a real 2xx; root-reachability probe explicitly opts out (requireOk: false) to keep its existing reachability-only semantics (already encoded in diagnostics-classify.test.ts). - docs-site/vs-termux: architecture explainer described /session/{id}/events and /session/{id}/chat, neither of which exists; the app actually uses /global/event and /session/{id}/prompt_async (src/lib/sdk.ts). - docs-site/features: opencode repo link pointed at github.com/opencode-ai/opencode instead of github.com/sst/opencode, the org used everywhere else in the docs. - Stale "v0.4.3" version strings (README x3, docs-site/download x2) bumped to v0.4.7, the actual latest shipped release confirmed live on GitHub Releases and the F-Droid repo index. Fixed the associated Android min-OS contradiction (7.0 vs 8.0+) to 7.0, matching the live F-Droid manifest's minSdkVersion 24. - Dropped stale "GPT-4" model naming on 3 docs-site pages, consistent with the model-agnostic policy already applied to the Play listing in #83. See PR body for the found-not-fixed list (Play Console listing drift, GitHub Sponsors not enabled, a Cloud-vs-waitlist messaging conflict) that need a product/human decision rather than a code fix. Co-authored-by: Claude Fable 5 --- CONTRIBUTING.md | 2 +- README.md | 8 ++++---- docs-site/comparison/index.html | 2 +- docs-site/download/index.html | 8 ++++---- docs-site/features/index.html | 4 ++-- docs-site/vs-termux/index.html | 6 +++--- src/lib/diagnostics.ts | 13 ++++++++++--- 7 files changed, 25 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dae1cc0..e01fba1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,7 +84,7 @@ You need a running [opencode](https://github.com/sst/opencode) server to test th ```bash # Install opencode -npm install -g opencode +npm install -g opencode-ai # Start it in server mode on all interfaces OPENCODE_SERVER_PASSWORD=devpassword opencode serve --hostname 0.0.0.0 --port 4096 diff --git a/README.md b/README.md index ed2c08e..d6d0ded 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ There are **three working ways** to install OpenCode Mobile today, all for Andro ``` https://dzianisv.github.io/opencode-mobile/fdroid/repo ``` - In the F-Droid app: **Settings → Repositories → + (add)** and paste the URL above. Current version: **v0.4.3**. + In the F-Droid app: **Settings → Repositories → + (add)** and paste the URL above. Current version: **v0.4.7**. 3. **Direct signed APK** — download the latest release and install it manually: **https://github.com/dzianisv/opencode-mobile/releases/latest** @@ -64,7 +64,7 @@ OpenCode Mobile is a React Native / Expo app that brings the power of the [openc ## Get OpenCode Mobile -Package: `cc.agentlabs.opencode` · Android only · current version v0.4.3 +Package: `cc.agentlabs.opencode` · Android only · current version v0.4.7 | Channel | Status | How | |---|---|---| @@ -86,7 +86,7 @@ Package: `cc.agentlabs.opencode` · Android only · current version v0.4.3 ```bash # Install opencode (if you haven't already) -npm install -g opencode +npm install -g opencode-ai # Run opencode in server mode OPENCODE_SERVER_PASSWORD=yourpassword opencode serve --hostname 0.0.0.0 --port 4096 @@ -137,7 +137,7 @@ OpenCode Mobile is a thin client. It speaks the opencode HTTP + SSE API: listing ## Project Status -**Current version: v0.4.3** +**Current version: v0.4.7** | Feature | Status | |---|---| diff --git a/docs-site/comparison/index.html b/docs-site/comparison/index.html index 3d2d448..9978ad3 100644 --- a/docs-site/comparison/index.html +++ b/docs-site/comparison/index.html @@ -198,7 +198,7 @@ td:first-child{font-weight:500} Bring your own LLM - Yes — any provider opencode supports (Claude, GPT-4, local models) + Yes — any provider opencode supports (Claude, GPT, Gemini, local models) OpenAI models only GitHub Copilot models only diff --git a/docs-site/download/index.html b/docs-site/download/index.html index 64ed753..0fc0af6 100644 --- a/docs-site/download/index.html +++ b/docs-site/download/index.html @@ -42,11 +42,11 @@ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": "OpenCode Mobile", - "operatingSystem": "Android 8.0+", + "operatingSystem": "Android 7.0+", "applicationCategory": "DeveloperApplication", "description": "Open-source Android client for the OpenCode AI coding agent. Connect to an opencode server you run yourself and code from your phone.", "downloadUrl": "https://github.com/dzianisv/opencode-mobile/releases/latest/download/app-release.apk", - "softwareVersion": "0.4.3", + "softwareVersion": "0.4.7", "license": "https://github.com/dzianisv/opencode-mobile/blob/main/LICENSE", "author": { "@type": "Organization", "name": "VIBE TECHNOLOGIES, LLC" }, "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" } @@ -171,7 +171,7 @@ footer a{margin:0 8px} OpenCode Mobile is the free, open-source Android client for the OpenCode AI coding agent. Install it from the F-Droid repository for automatic updates, or grab the signed APK directly. - No account, no tracking, no ads. Current version 0.4.3. + No account, no tracking, no ads. Current version 0.4.7.

Before you install: OpenCode Mobile is a client — it connects to an @@ -252,7 +252,7 @@ footer a{margin:0 8px}

What do I need to run it?

- An Android 8.0+ device, a machine running opencode serve, a way to reach that machine + An Android 7.0+ device, a machine running opencode serve, a way to reach that machine from your phone (Tailscale, a Cloudflare/ngrok tunnel, or the same local network), and your own AI provider key. The setup guide walks through every step in about 15 minutes.

diff --git a/docs-site/features/index.html b/docs-site/features/index.html index c6365a1..3910444 100644 --- a/docs-site/features/index.html +++ b/docs-site/features/index.html @@ -50,7 +50,7 @@ "offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" }, "featureList": [ "Real-time AI coding sessions via Server-Sent Events", - "Multi-model support: Claude, GPT-4, Gemini, Ollama, and any opencode-compatible model", + "Multi-model support: Claude, GPT, Gemini, Ollama, and any opencode-compatible model", "Tailscale and VPN connectivity for private server access", "Streaming diff and tool-use output as the agent works", "Full session history with scroll and search", @@ -234,7 +234,7 @@ footer a:hover{color:var(--accent)}

System requirements

diff --git a/docs-site/vs-termux/index.html b/docs-site/vs-termux/index.html index 9f63f90..fd15797 100644 --- a/docs-site/vs-termux/index.html +++ b/docs-site/vs-termux/index.html @@ -436,9 +436,9 @@ OPENCODE_SERVER_PASSWORD=yourpassword opencode serve --hostname 0.0.0.0 --port 4 happens:

    -
  1. The app opens an SSE stream to your opencode server's /session/{id}/events endpoint.
  2. -
  3. You type a prompt — the app posts it to /session/{id}/chat.
  4. -
  5. The server forwards the prompt to your configured LLM (Claude, GPT-4, a local model, whatever you set up).
  6. +
  7. The app opens an SSE stream to your opencode server's /global/event endpoint.
  8. +
  9. You type a prompt — the app posts it to /session/{id}/prompt_async.
  10. +
  11. The server forwards the prompt to your configured LLM (Claude, GPT, a local model, whatever you set up).
  12. Tokens stream back from the LLM to the server, and from the server over SSE to the app.
  13. When the agent proposes a file change, the app renders the diff and waits for your approve/reject tap.
  14. Your approval goes back to the server, which applies the change to the real filesystem.
  15. diff --git a/src/lib/diagnostics.ts b/src/lib/diagnostics.ts index 2ce030a..1cac275 100644 --- a/src/lib/diagnostics.ts +++ b/src/lib/diagnostics.ts @@ -36,13 +36,20 @@ export interface DiagnosticReport { timestamp: string } -async function timedFetch(name: string, target: string, init?: RequestInit): Promise { +// requireOk: whether a non-2xx HTTP response counts as a probe failure. +// The health probe must actually succeed (2xx) to mean "the server works" — +// otherwise a 401/403/404/500 response was being reported as ok:true, which +// made classify() short-circuit to "connection actually works now" even when +// auth failed or the server errored. The root probe only checks reachability +// (any HTTP response, even an error status, proves something is listening). +async function timedFetch(name: string, target: string, init?: RequestInit, opts?: { requireOk?: boolean }): Promise { + const requireOk = opts?.requireOk ?? true const start = Date.now() const controller = new AbortController() const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS) try { const res = await fetch(target, { ...init, signal: controller.signal }) - return { name, target, ok: true, status: res.status, durationMs: Date.now() - start } + return { name, target, ok: requireOk ? res.ok : true, status: res.status, durationMs: Date.now() - start } } catch (error: unknown) { const err = error as { name?: string; message?: string; cause?: unknown } const aborted = err?.name === "AbortError" @@ -80,7 +87,7 @@ export async function probeConnection(url: string, auth?: { username: string; pa const base = `${parsed.scheme}://${parsed.host}:${parsed.port}` ;[health, root, internet] = await Promise.all([ timedFetch("health", `${base}/global/health`, { headers }), - timedFetch("server-root", `${base}/`, { headers }), + timedFetch("server-root", `${base}/`, { headers }, { requireOk: false }), timedFetch("internet", INTERNET_CHECK_URL), ]) } else {