Commit Graph

20 Commits

Author SHA1 Message Date
Den
20806d41ba chore(release): 0.4.8 (versionCode 35) — ship retention improvements (#112)
Bundles the unreleased work sitting on main since v0.4.7:
- Offline demo mode (#108) — value without a server
- First-run clarity + fast-fail connect timeout (#107)
- Directory-browser stuck-state fix (#106)

Version + versionCode bumped and a user-facing changelog added so the owner
can cut the release (tag v0.4.8 / dispatch publish-play-store.yml). Does NOT
itself publish — releasing to production stays an owner action.


Claude-Session: https://claude.ai/code/session_01T12AhSnQVrSxNnvwfCx2z6

Co-authored-by: engineer <engineer@macbookpro.lan>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:05:36 -07:00
Den
baf73058fd feat(i18n): add i18n infrastructure + Simplified Chinese, localize Settings screen (refs #68) (#97)
- Add expo-localization, i18next, react-i18next (versions aligned with
  Expo 54 / RN 0.81)
- src/lib/i18n/locale-resolve.ts: pure locale-resolution helpers
  (system tag -> supported catalog, with en fallback), unit-tested via
  node --test with no RN imports
- src/lib/i18n/config.ts: i18next init wired to expo-localization
  device detection, en.json + zh-Hans.json catalogs
- Persist a locale preference (system | en | zh-Hans) in the settings
  zustand store, applied immediately via i18next.changeLanguage
- Wire I18nextProvider in app/_layout.tsx
- Localize the Settings screen (~28 strings) as the reference pattern
  for extracting user-facing strings, with a language picker row and
  reviewed Simplified Chinese translations

Other screens (session/[id], connection/*, index, chat components)
are deferred follow-up — issue #68 stays open for that work.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:47:18 -07:00
engineer
b4483887ec merge: feat/activation-analytics — consent-gated PostHog activation funnel (reviewed: APPROVE after fixes) 2026-07-16 16:09:27 -07:00
engineer
ec0a0a04d3 merge: fix/sentry-sourcemaps — metro debug-ID injection + release/dist alignment (reviewed: APPROVE) 2026-07-16 15:58:45 -07:00
engineer
027c529ce5 merge main (feat/feedback-automation) into feat/activation-analytics
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 15:57:13 -07:00
engineer
42ceea3e1f fix(sentry): repair Android source-map upload (debug IDs + release/dist match)
Releases 0.4.3-0.4.7 uploaded zero source-map files to Sentry, leaving every
JS frame unsymbolicated (app:///index.android.bundle:1). Root-caused two
independent bugs:

1. No metro.config.js existed, so Metro never ran Sentry's debug-ID
   injection. Without an embedded debug ID, sentry.gradle's upload task
   falls back to matching source maps to events by release/dist string
   alone (see has-sourcemap-debugid.js check in sentry.gradle) - and that
   fallback was broken (see #2). Added metro.config.js wrapping Expo's
   default config with getSentryExpoConfig from @sentry/react-native/metro,
   the officially documented path for Expo + debug-ID symbolication.

   The installed @sentry/react-native@6.14.0 could not actually bundle with
   this enabled: its metro integration does a hard `require("metro/src/lib/
   countLines")`, a deep path metro 0.83.x (bundled by Expo SDK 54) no
   longer exposes via its package.json `exports` map, crashing every build.
   Bumped to ~6.22.0 (package.json:18), which vendors countLines and adds
   metro/private/* fallbacks for other deep metro imports. Verified via a
   real `npx expo export:embed` run: bundle and source map now share a
   matching `debugId`.

2. sentry.gradle's default release/dist for the upload is
   `${applicationId}@${versionName}+${versionCode}` (computed from
   android/app/build.gradle), which never matched what Sentry.init() reports
   at runtime (`opencode-mobile@${app.json version}`, src/lib/sentry.ts:33-34).
   Every source map was therefore filed under a release Sentry never
   queries. Added a "Set Sentry release identifiers" step to build.yml,
   publish-play-store.yml, and publish-fdroid.yml that exports
   SENTRY_RELEASE/SENTRY_DIST from app.json's version before the Gradle
   build step, forcing an exact match.

Also filled in organization/project on the `@sentry/react-native/expo`
plugin in app.json (previously a bare string, which only warned "Missing
config for organization, project" and relied on env-var fallback) so
android/sentry.properties is generated deterministically instead of by
accident/history.

Verified locally (no push - GitHub is down, consolidating to local main):
- npx expo export:embed (real Metro bundle) succeeds and embeds a matching
  debugId in both index.android.bundle and its .map
- npm run typecheck: clean
- npm test: 81/81 passing
- Full ./gradlew Android build not verified: this machine has no
  ANDROID_HOME/SDK and a JDK/Gradle-wrapper version mismatch unrelated to
  this change; CI's Java 17 + Android SDK toolchain is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 15:51:31 -07:00
engineer
ace8c19816 feat(analytics): add consent-gated activation-funnel analytics via PostHog
Installs are up 615% but 7-day retention is ~0% and we had no analytics SDK
to see where users drop off. Adds a thin PostHog wrapper (src/lib/analytics.ts)
that tracks app_opened, connection_form_submitted, connection_attempted,
connection_succeeded/failed (with a coarse error_class, e.g. the known 401
auth bug), message_sent, and response_received.

PostHog was chosen over Aptabase for its GMS-free JS-only RN SDK (fine for
the F-Droid/no-Firebase build), EU-hosted/self-host option, and generous
free tier. Analytics shares the exact same consent flag as Sentry
(telemetry.ts now gates both) so zero network calls happen without explicit
opt-in.

Requires a new EXPO_PUBLIC_POSTHOG_KEY CI secret (wired into build.yml,
publish-fdroid.yml, publish-play-store.yml, and documented in
publish-app-store.yml alongside the existing Sentry secrets).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 15:48:16 -07:00
engineer
d3ee3d9e82 feat(feedback): prompt for a store review after successful sessions
Add expo-store-review (SDK 54-matched via `expo install`) and wire a
one-time in-app rating prompt into the SSE busy->idle "session completed"
transition in stores/events.ts — the same signal that already drives the
"Task completed" notification, so it only fires on genuine success, never
on session.error.

State (success count, one-time "asked" flag) persists in expo-secure-store,
mirroring the consent pattern in telemetry.ts. The threshold check is split
into store-review-policy.ts, free of expo imports, so it's unit-testable
with plain `node --test` (same split as buildAuth in auth.ts).

F-Droid/Play-Services-absent safety comes from the library itself:
StoreReview.isAvailableAsync() resolves false there, so requestReview() is
never called and there's no store-URL fallback configured in app.json.
2026-07-16 15:46:28 -07:00
Dennis V
cc0f5217b1 release: bump to v0.4.7 (versionCode 34)
SSE disconnect banner, backgrounded permission notifications,
scrubUrl unit tests (13 tests, 81/81 suite green).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 16:39:43 +00:00
Dennis V
7616f97326 release: bump to v0.4.6 (versionCode 33)
Sessions loading regression now properly tested in CI — CUA sessions_reload
phase confirmed passing. Also: support email, descriptions, screenshots.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 04:49:49 +00:00
Dennis V
ae124d7311 release: bump app.json/package.json to 0.4.5, add changelog 32.txt
- app.json 0.4.4 -> 0.4.5 (matches build.gradle versionName)
- package.json 0.4.3 -> 0.4.5
- fastlane changelog 32.txt: notes #32 fix (sessions list shows workspace projects)
2026-06-22 05:01:44 +00:00
engineer
b7b3f2fc27 fix(fdroid): force all expo modules to build from source
Set expo.autolinking.buildFromSource: [".*"] so F-Droid mainline's
scanner doesn't see the prebuilt .aar + -sources.jar files that Expo
ships inside every expo-* npm package's local-maven-repo/ directory.
When this flag is set, expo-modules-autolinking routes each module
through a Gradle source build instead of consuming the prebuilt AAR.

Verified: `./gradlew assembleRelease` succeeds (11 min, 92 MB APK)
with this flag and no prebuilt AARs consumed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 00:49:41 -07:00
engineer
c66f4fb858 release(fdroid): v0.4.3 — fix self-hosted F-Droid publish (v2-only signing)
The self-hosted F-Droid repo (https://dzianisv.github.io/opencode-mobile/fdroid/repo)
has been stuck at v0.4.1 because publish-fdroid crashed in androguard parsing the
CI APK's v2+v3 signature block pair ('NoOverwriteDict' object has no attribute
'append'). Force v1+v2-only signing: gradle flags for local builds, plus a
deterministic apksigner re-sign step in the workflow (expo prebuild regenerates
build.gradle, so the workflow step is the real guarantee). Bump to v0.4.3 /
versionCode 5 so a fresh tag re-runs the publish with the verified bug fixes
(#10 scope fixes, send-error fix) included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 00:58:41 -07:00
engineer
bed0b6f622 refactor(sessions): single source of truth for session directory scope (#10)
Extract the list/create scope rule into sessionScopeDirectory() so loadSessions
and createSession can no longer drift apart — the root cause of #10 (sessions
empty after connect/create). Add a zero-dependency node:test regression guard
proving both paths resolve identically across all inputs, a 'test' npm script,
and exclude test files from the app typecheck.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 23:46:48 -07:00
engineer
0d25b3705c chore: release v0.4.2 — first post-rename build (cc.agentlabs.opencode)
First tagged release after the ai.opencode.mobile -> cc.agentlabs.opencode
rename. Produces a signed cc.agentlabs.opencode APK on the GitHub release and
refreshes the self-hosted F-Droid repo to the current package — prerequisite
for the F-Droid mainline / IzzyOnDroid submissions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 13:41:59 -07:00
engineer
d0b118e265 chore: release v0.4.1 — CI improvements & cleanup
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-27 09:17:27 -07:00
Den
32f7af4e11 fix(sessions): recover home-scoped list after fresh connect
* fix(sessions): use active connection client directly, remove roots filter

Root cause A: loadSessions was calling clientForDirectory(serverHome) which
scoped the session list to /home/azureuser — a different project than the
server's active CWD. Sessions in the current project (e.g. opencode-mobile)
were never returned.

Root cause B: roots:true filtered out sessions that have a parentID (sub-task /
AUTO-REVIEW sessions), hiding valid sessions from the list.

Fix: use connState.client directly (the connection's active directory) and drop
the roots filter so all sessions for that project are visible.

Also adds a verify_session_list CUA smoke scenario that navigates back to the
sessions tab after creating a session and asserts the list is non-empty —
covering the regression path that was previously untested.

* fix(sessions): fetch serverHome in addConnection so loadSessions shows correct sessions

Root cause: addConnection() built the HTTP client but never fetched serverHome
(only loadConnections and setActiveConnection did). When the user adds a new
connection (fresh install / first sign-in), serverHome = null, so loadSessions
fell through to connState.client (the server's CWD). On this dev server the CWD
is the deploy directory — 11 old May-19 sessions that are not the user's recent
work sessions.

Fix: addConnection now fetches currentProject + serverHome via the same
Promise.all as setActiveConnection, before calling set(). This ensures
loadSessions immediately uses clientForDirectory(serverHome) → the global
project → the user's actual recent parent sessions.

Also adds --opencode-url flag to the CUA smoke script, which appends a
connect_and_verify_sessions scenario that reproduces the regression:
  python scripts/android-cua-smoke.py --opencode-url http://100.108.64.76:4096

* fix(sessions): recover home scope after fresh connect

Resolve stale deploy-only session list by recovering server home during first load and keeping regression coverage in default Android CUA smoke and CI.

* chore(release): bump version to 0.4.0
2026-05-26 19:50:17 -07:00
Den
2b9b571d6e feat(privacy+dist): telemetry consent gate + app store distribution prep (#4)
* fix(security): fail closed on biometric init error

H-03: setting isAuthenticated: true on initialization failure was a
security bypass — any crash during biometric setup granted full access.
Fail closed instead; user sees auth prompt on next open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): use Crypto.randomUUID for connection IDs

H-04: Math.random() is not cryptographically random. Connection IDs are
used as SecureStore key suffixes; switch to expo-crypto randomUUID for
a secure source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): pin expo-crypto to ~15.0.9

15.0.10 does not exist on npm; ~15.0.9 is the latest stable in the 15.x series compatible with Expo SDK 54.

* feat: add OpenCode Connect coming-soon waitlist card

Adds a discoverable 'OpenCode Connect — Coming Soon' card to the
add-connection quick-connect screen. Users can enter their email and
tap 'Join Waitlist' to send a pre-filled mailto. No backend required.

* fix(cua): detect actual screen dimensions and fix JSON parsing

- Get real screen size via `wm size` instead of hardcoding 1080x2400;
  emulator is 1080x1920 so y-coordinates were systematically off
- Extract first JSON object via regex when model returns multiple objects
- Use AZURE_OPENAI_MODEL env var for deployment name (defaults gpt-5.4)
- Add AZURE_DEV_AI_* path for Azure AI Foundry endpoints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(security): SHA-pin upload-google-play and sanitize notification bodies

M-02: Pin r0adkll/upload-google-play to commit SHA e738b9d (v1.1.5)
to prevent supply-chain hijack via tag mutation.

M-03: Sanitize all push notification bodies — strip control chars,
truncate to 200 chars. Prevents server-supplied strings (error messages,
file paths from permission patterns, session titles) from leaking
unbounded text into the OS notification drawer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(privacy): add telemetry consent gate for Sentry crash reporting

Sentry was always-on, violating F-Droid anti-feature policy and user
trust norms. Now gated behind explicit opt-in:

- First-launch consent modal (TelemetryConsentModal) shows once on
  fresh install; user can Allow or Decline.
- Consent state persisted in expo-secure-store (survives restarts).
- Settings > Privacy section: crash reporting toggle + privacy policy link.
- initSentry() called only after consent granted — not on app start.

Closes #3 (partial)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(config): add real icons and complete iOS/Android app.json config

- Add 1024×1024 app icon, 432×432 adaptive icon foreground, 200×200 splash
- iOS: push notification entitlement (aps-environment: production), speech/
  microphone/camera/photo usage descriptions for future features, disable
  ITSAppUsesNonExemptEncryption
- Android: adaptive icon with dark background (#0F172A), versionCode: 1
- expo-notifications plugin wired in app.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(dist): add iOS CI workflow, README rewrite, CONTRIBUTING, and LICENSE

- publish-app-store.yml: EAS Build + TestFlight submission; runs on tag/release/
  workflow_dispatch; bumps ios.buildNumber from github.run_number
- README: full rewrite — features, install badges, connection guide, contributing
- CONTRIBUTING.md: contribution guide for OSS contributors
- LICENSE: MIT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(dist): add store listings, strategy, privacy policy, F-Droid/IzzyOnDroid templates

- distribution/strategy.md: monetization strategy (free client + opencode Cloud)
- distribution/play-listing.md: Google Play store copy (name, description, tags)
- distribution/app-store-listing.md: App Store listing copy
- distribution/privacy-policy.{md,html}: GDPR-compliant privacy policy
- distribution/PLAY_CONSOLE_SETUP.md: Play Console setup runbook
- distribution/ios-enrollment-runbook.md: Apple Developer Program enrollment steps
- distribution/SIGNING-KEY-FINGERPRINTS.md: keystore fingerprint for reproducible builds
- distribution/fdroid-submission/: F-Droid metadata template
- distribution/izzyondroid-submission/: IzzyOnDroid submission template
- distribution/whatsnew/: Play Store release notes (en-US)
- distribution/whatsnew-ios/: TestFlight release notes
- distribution/play-graphics/: Play Store screenshot placeholders
- distribution/app-store-graphics/: App Store screenshot placeholders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(telemetry): handle SecureStore failure + Android back button

- add .catch() on loadTelemetryConsent() so SecureStore rejection
  shows the consent modal instead of blocking startup forever
- add onRequestClose={onDecline} to Modal so Android back button
  records the decline rather than silently dismissing
- fix catch block in telemetry.ts to not clobber _resolved when
  SecureStore read fails mid-session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): run gradlew clean to prevent stale modules.json duplicate

Sentry Gradle plugin writes modules.json to src/main/assets; cached
build intermediates contain an old copy → mergeReleaseAssets fails
with 'Duplicate resources'. Running clean before assembleRelease
clears the intermediate state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): remove android build output cache causing duplicate modules.json

Caching android/app/build/intermediates and android/app/.cxx causes
two issues:
1. Stale modules.json in intermediates → Duplicate resources error
2. .cxx CMake artifacts reference absolute paths → ninja clean fails

Keeping only Gradle distribution cache (~/.gradle) which is safe.
Expo prebuild regenerates android sources fresh each run anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 17:42:03 -07:00
Den
30be6636c2 feat(diagnostics): active connect-failure triage + Sentry + in-app share (#2)
* feat(diagnostics): active connect-failure triage + Sentry + in-app share

Replaces the opaque "Connection Failed" / "Network request failed" dead-end
with on-device diagnostics that classify *why* a connect attempt failed.

On failure (quick connect and edit-connection test), the app now:
- runs parallel probes: target /global/health, target root, and a public
  204 endpoint (internet reachability check)
- classifies the cause: malformed-url, no-internet, server-unreachable,
  health-failed, tls-error, timeout
- shows a plain-English summary + a "Share report" button that copies a
  full report (target URL, per-probe results w/ error.cause, device/app
  info, recent log ring-buffer) to the clipboard and opens the share sheet
- captures the same structured context to Sentry (auto-upload), gated on
  EXPO_PUBLIC_SENTRY_DSN so dev/CI builds work without secrets

New: src/lib/logbuffer.ts (ring buffer + logger), src/lib/diagnostics.ts
(regex URL parse — Hermes URL is incomplete — probe + report + share),
src/lib/sentry.ts (no-op-without-DSN wrapper, scrubs basic-auth from URLs).

Wired Sentry.wrap around RootLayout and initSentry() at module load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci(sentry): wire Sentry DSN + source-map upload env into build; bump to 0.2.2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 17:07:44 -07:00
Ubuntu
1843a7c37e Initial commit: OpenCode mobile app (extracted from opencode monorepo) 2026-05-17 19:52:54 +00:00