Commit Graph

22 Commits

Author SHA1 Message Date
xieyao
f0185cbd23 feat: full Chinese (zh-Hans) localization — i18n all hardcoded strings, zh-CN download page
Some checks failed
Daily Product Intelligence / report (push) Has been cancelled
Play Store Review Triage / triage (push) Has been cancelled
Sentry noise-gate report / report (push) Has been cancelled
2026-09-09 04:05:43 +08:00
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
Den
2f81d34200 fix(waitlist): queue + retry failed signups instead of silently opening mailto (#165)
A signup that hit a network error, the 8s timeout or a 5xx was handed straight
to a `mailto:` composer. That path is lossy by design: it only works if the user
actually presses send, and if we keep reconciling the support inbox into Brevo
list 4 forever (AGE-61's hourly job). 20 of 21 signups were lost that way before
that reconciler existed, and Play's active base is ~100% on v0.4.10+ — so this
was current builds leaking, not just the ~436 stale sideloads.

Now:
- Failed-but-retryable signups are persisted on-device
  (`opencode.waitlist.pending.v1`, AsyncStorage) and retried on every app
  foreground (`app/_layout.tsx`) and on the Add Connection screen mount.
- 4xx stays non-retryable: the server will never accept that address, so we ask
  the user to fix it instead of queueing garbage forever.
- `mailto:` is now only ever opened by an explicit user tap ("Still not working?
  Email us instead"), shown after 3 failed attempts, or offered in an alert when
  device storage itself refuses the write — never as the silent default.
- The UI tells the truth: "Saved on this device — we'll finish signing you up as
  soon as you're back online" instead of implying it was sent.
- `WaitlistResult.fallback` -> `retryable`, `shouldFallbackToMailto` ->
  `isRetryableFailure`: the decision is about retry, not about mail.

Queue policy: dedupe by email, cap 5 entries, 30-day TTL, corrupt/foreign JSON
is discarded rather than replayed. Storage and the clock are injected so the
whole thing runs under `node --test` (16 new tests, incl. the acceptance case:
offline signup -> queued -> reconnect -> reaches the server, no mail client).

Also commits the AGE-61 measurement artifacts that were only ever local
(`distribution/waitlist-signup-path-coverage.md`, `scripts/play-version-share.mjs`)
and updates the doc's "current builds still leak" section, which this fixes.

Refs AGE-87, AGE-61.

Co-authored-by: engineer <engineer@macbookpro.lan>
2026-08-14 01:30:23 -07:00
Den
df4a3618c4 fix(connection): fix 6 correctness bugs in auth/connect/diagnostics flow (#131)
1. buildRequestHeaders: UTF-8-encode Basic-auth credentials before btoa()
   so non-ASCII usernames/passwords don't throw (Hermes' btoa is Latin1-only
   and the throw was an unhandled rejection that hung the connect spinner).

2. diagnostics classify(): check root.ok (server reachable) before
   !internet.ok, so a reachable-but-failing server (e.g. wrong auth) is no
   longer misdiagnosed as "no internet" just because the public-internet
   probe also failed (captive portal, Tailscale-only network, etc).

3. sdk.ts createClient: strip trailing slashes from baseUrl once, so a
   trailing-slash URL from Advanced mode / Edit screen doesn't produce a
   double slash on every request path.

4. add.tsx / [id].tsx: wrap addConnection/updateConnection in try/catch so
   a SecureStore failure after a successful test resets the spinner and
   shows an alert instead of hanging forever. Adds
   connection.shared.alerts.saveFailedTitle/saveFailedMessage (en + zh-Hans).

5. add.tsx / [id].tsx: build the diagnostics probe's auth with buildAuth()
   instead of a hand-rolled expression, so the probe reproduces the real
   request's credentials (previously Quick Connect's password-only case
   sent no auth to the probe at all).

6. add.tsx handleQuickConnect: stop sending the shared `username` state,
   which could carry a stray value typed earlier in Advanced mode and
   silently override the "opencode" default after "Back to Quick".


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

Co-authored-by: engineer <engineer@macbookpro.lan>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:04:33 -07:00
Den
f86265aa7c fix(security): re-lock biometric app-lock on background; persist edited password (#125)
Two issues from a security review of the credential/auth path (the review also
verified the fundamentals are solid — passwords in SecureStore, Sentry/analytics/
Chatwoot all scrub secrets).

1. HIGH: biometric app-lock never re-armed. authenticate() sets isAuthenticated
   =true once at cold start and lock() was never called (no AppState listener) —
   so 'Require Biometric to Open' was fully bypassable: after one unlock, anyone
   with brief physical access could reopen a backgrounded app straight into
   session history and connection details for the life of the JS process. Now an
   AppState 'background' listener calls lock() when the toggle is on. Fires on
   'background' only, so the biometric prompt / app switcher (transient
   'inactive') don't cause spurious re-locks.

2. Editing a connection's password did nothing: the edit screen's password field
   was never passed to updateConnection, which never wrote PASSWORDS_PREFIX — so
   a user rotating a server password silently kept using the old one. updateConnection
   now takes an optional password and writes it to SecureStore (blank = keep
   existing, since the field loads empty).

typecheck clean, 187/187 tests.


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-18 08:49:42 -07:00
Den
f945cab5c6 feat(i18n): extract remaining screens into en/zh-Hans catalogs; full Simplified Chinese coverage (closes #68) (#103)
Extends the i18n infra from #97 (Settings screen only) to the rest of the
app: session chat screen, connection add/edit/list screens, sessions list
(incl. directory grouping from #96), navigation titles, notifications
category metadata, error boundary, telemetry consent modal, auth gate, and
every chat UI component (permission/question prompts, status indicator,
model/variant pickers, directory switcher/browser, reasoning block, tool
call card, session info).

- 244 new keys added to en.json/zh-Hans.json with reviewed, natural
  Simplified Chinese (not machine-garbage), keeping key sets identical.
- User content, server URLs, code snippets, log/error-detail text, and
  diagnostics-classify.ts (pure dependency-free module feeding Sentry/
  support reports) are intentionally left untranslated per scope.
- Interpolation used for counts/names (e.g. reconnect attempt, files
  count, connection name in delete confirmations); categoryMeta/
  CONNECTION_TYPES switched to labelKey indirection since they're
  module-level constants evaluated before i18next is guaranteed ready.
- Added src/lib/i18n/catalog-parity.test.ts (node --test) asserting
  en.json/zh-Hans.json expose identical key sets and no empty values,
  to catch future locale drift.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:52:41 -07:00
Den
3b6dab8c27 fix(auth): stop infinite SSE retry on 401/403, surface auth failures (#79)
* fix(auth): stop infinite SSE retry on 401/403 and surface auth failures

Root cause (Sentry OPENCODE-MOBILE-1, 309 events / 65 users): auth is static
HTTP Basic and no code path treated 401 specially. The SSE reconnect loop in
events.ts retried on a fixed backoff regardless of cause, so a bad password
spammed Sentry and drained battery forever with zero user feedback.
Advanced-mode connection save also had no pre-flight check and silently
persisted bad credentials as the active connection.

- src/lib/api-error.ts: new pure ApiAuthError/isAuthStatus/isAuthError module
  (node --test covered) so 401/403 are distinguishable from other failures.
- src/lib/sdk.ts: request()/events() now throw ApiAuthError for 401/403
  instead of a generic Error.
- src/stores/events.ts: the SSE loop stops retrying on an auth error and sets
  a new `authError` flag instead of reconnecting forever; other errors keep
  the existing backoff. Fires connection_failed (source: sse, error_class:
  unauthorized) so it's visible in the existing funnel.
- app/(tabs)/index.tsx: sessions screen shows an "Authentication Failed"
  state with a link to the connection edit screen when authError is set.
- app/connection/[id].tsx: saving edited credentials for the active
  connection now reconnects SSE immediately instead of requiring an app
  restart.
- app/connection/add.tsx: Advanced-mode save now runs the same testConnection
  pre-flight as Quick Connect and shows the same "Connection Failed" alert
  (with diagnostics/share-report) instead of silently saving bad credentials.

Closes #76

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

* fix(auth): add Retry button on 401 error state, widen ConnectionTestSource

- Authentication Failed screen now offers Retry alongside Check
  Credentials, calling events store's connect() directly to restart
  the SSE state machine on transient 401s without leaving the app.
- Widen ConnectionTestSource to include 'sse' (events.ts:389's
  connection_failed track call) and note the activation funnel only
  filters on source=onboarding.

Addresses PR #79 review follow-ups.

---------

Co-authored-by: engineer <engineer@gray-knight-m1.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 02:22:53 -07:00
Den
0fdfb54d9d feat(waitlist): capture OpenCode Connect signups via beta-signup API (#92)
* feat(waitlist): capture OpenCode Connect signups via beta-signup API (closes #87)

The 'OpenCode Connect — Coming Soon' card only opened a raw mailto: link,
so waitlist signups existed solely as loose emails in the support inbox
with no backend capture.

- POST the signup to https://opencode.agentlabs.cc/api/beta-signup
  (OpenCodeMobileSite route -> Brevo list) tagged with
  source: "opencode-connect-waitlist". The route ignores unknown fields
  today, so the tag is forward-compatible.
- Pure payload/validation/fallback logic lives in src/lib/waitlist.ts
  (no react-native imports, dependency-injected fetch, AbortController
  timeout like diagnostics.ts) with node --test coverage.
- Graceful degradation: transport failures and 5xx fall back to the old
  mailto: path so the signup still reaches the inbox; 4xx asks the user
  to fix their email. Success shows an inline confirmation state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(waitlist): handle mailto fallback failure, name 502 in fallback test

Review findings: Linking.openURL was fire-and-forget, so a device with
no mail app failed the recovery path silently — await it and alert with
a manual instruction instead. Test title now names 502 (Brevo failure)
as an explicit fallback case.

Refs #87

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 23:32:41 -07:00
engineer
3a724eb818 merge: test/activation-e2e — Maestro activation E2E + mock opencode server (reviewed: APPROVE after fixes) 2026-07-16 17:24:32 -07:00
engineer
c3cac2b8e5 fix(analytics): address review findings on activation-funnel events
- app_opened now also fires on the consent-grant transition (modal Allow /
  Settings toggle), not just cold start with prior consent — the true first
  session was emitting nothing and session 2 got mislabeled is_first_open.
  trackAppOpened() is guarded once-per-JS-session so revoke->regrant cannot
  double-count.
- testConnection() takes a source ('onboarding' | 'edit_test') carried on
  connection_attempted/succeeded/failed so the funnel can filter out the
  edit screen's repeat-tester noise.
- Aborted runs no longer count: abortedSessions set (in sessions.ts, read by
  events.ts which already imports it — no new import cycle), marked after a
  successful abort call, cleared on busy, and checked on busy->idle for BOTH
  response_received and recordSuccessfulSession().
- Consent revocation now DROPS buffered events instead of flushing them:
  PostHog's optOut() only blocks new captures and shutdown() drains the queue
  over the network, so ConsentGatedPostHog overrides the public fetch()
  transport to answer with a synthetic 200 post-revoke — shutdown clears the
  persisted queue and timers with zero bytes leaving the device. Re-grant
  calls optIn() to clear the persisted SDK opt-out flag.
- classifyConnectionError extracted to pure analytics-classify.ts with
  node --test coverage (same pattern as store-review-policy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 16:04:54 -07:00
engineer
01dd0191b3 test(activation): add Maestro E2E coverage for the activation flow
Adds deterministic end-to-end coverage for first-open -> telemetry consent
-> server URL entry -> connect -> send first message -> receive reply,
targeting the 0%-7-day-retention investigation (GitHub issue #76).

- tests/fixtures/mock-opencode-server.ts: dependency-free HTTP+SSE stub
  matching the REAL client protocol (src/lib/sdk.ts) — REST + a single
  long-lived GET /global/event SSE stream, no WebSocket. Supports a
  --fail-auth mode that 401s every request to exercise the connect-time
  auth-failure class.
- .maestro/flows/activation-positive.yaml: consent -> quick connect ->
  new session -> send message -> assert streamed reply renders, with a
  screenshot at every step (positive-S1..S8).
- .maestro/flows/activation-negative-401.yaml: same setup against the
  --fail-auth server, asserts Quick Connect's existing "Connection Failed"
  alert is shown (not silently swallowed) and that the connection is not
  saved. Flags in comments that Advanced-mode Save (handleAdvancedSave)
  still has no testConnection() check and is a known, uncovered gap.
- testID props added (no restructuring) to the screens/components the
  flows drive: TelemetryConsentModal, connection/add.tsx, tabs/index.tsx,
  session/[id].tsx, MessageBubble.
- .github/workflows/activation-e2e.yml: new CI job — Android emulator via
  reactivecircus/android-emulator-runner, builds the debug-signed APK,
  starts both mock server instances, runs both Maestro flows, uploads
  screenshots via actions/upload-artifact. Kept separate from the existing
  vision-driven cua-smoke.yml, which needs a live server + LLM and isn't
  suited to tight deterministic regression assertions.
- .gitignore: Maestro takeScreenshot output is never committed.

Verified locally: mock server exercised standalone via curl (health,
project/current, path, session create, SSE event ordering, message
persistence) in both normal and --fail-auth modes; both Maestro flow
files validated as well-formed YAML; tsc --noEmit clean on all changed
files. No lint script exists in this repo (N/A). Full emulator execution
was not run — no Android SDK/emulator available in this environment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NJKAQ6HAikWGQK7PGZ5Y4E
2026-07-16 15:56:47 -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
Dennis V
f9b18a06f3 fix(privacy): stop telemetry on consent revocation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-14 06:30:09 +00:00
Dennis V
80cefa798d fix(support): correct typo connect@ → support@agentlabs.cc in connection error mailto
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 00:17:50 +00:00
Dennis V
1f476f1eab fix(support): update contact email to support@agentlabs.cc
Replace all @vibebrowser.app email addresses with @agentlabs.cc across
22 files including privacy policy, Play/App Store listings, fastlane
metadata, docs, README, CONTRIBUTING, eas.json, and in-app mailto links.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-23 00:17:06 +00:00
Den
a9822048e4 ux(quick-connect): hint users to Advanced for custom server username (#23) (#25)
Quick Connect now shows a one-line hint under the password field stating
that username defaults to 'opencode' and pointing to Advanced options for
servers using a custom OPENCODE_SERVER_USERNAME. Tap-target to switch
modes is included.

Closes #23

Co-authored-by: dzianisv <dzianis.varabyou@gmail.com>
2026-06-09 03:31:46 -07:00
engineer
42fa36f3cd fix(ux): 10 UI/UX bugs from pre-release audit (toward no-bug launch gate)
Found via parallel screen audit; each confirmed in code:
- AuthGate: auto-prompt biometrics on lock (useEffect was imported but unused)
- CodeBlock: horizontal scroll for long code lines (were wrapped/mangled)
- DiffView: horizontal scroll instead of numberOfLines=1 truncation
- chat: biometric-cancel on send shows feedback instead of silently dropping msg
- chat: send failure restores input + attachments and alerts
- chat: removed dead /compact + /clear builtin commands (advertised, no-op)
- sessions: delete + rename failures alert instead of silent; rename guarded
  against double-submit
- sessions: onRefresh spinner no longer hangs forever if a refresh rejects
- add/edit connection: validate URL has http(s):// scheme before save/test

typecheck clean, 65/65 unit tests pass. Runtime UI behavior still needs on-device
verification per the pre-posting test gate (HANDOFF §0b).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 05:34:12 -07:00
engineer
6563f5ef53 fix(ui): correct mirrored chat empty-state + clipped IP placeholder; add readiness-check skill; privacy page nav/meta
- app/session/[id].tsx: chat empty-state rendered mirrored on Android (inverted FlatList) — now an untransformed overlay
- app/connection/add.tsx: shortened clipped host placeholder
- .agents/skills/readiness-check: production-readiness gate (Play+F-Droid published, app+site health)
- docs/privacy: back-nav + canonical/theme-color/description meta
Verified: tsc clean, CUA smoke green (run 26814702062)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 06:16:22 -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
Den
e9991cb61b fix(connect): fix tailnet 'Connection Failed' (pasted-URL double scheme) + surface real error (#1)
* fix(connect): surface real connection error instead of generic dialog

testConnection swallowed the actual fetch error and returned a bare
boolean, so every failure collapsed to the same "Connection Failed"
text. On-device this made tailnet/LAN connect failures impossible to
diagnose (DNS vs timeout vs 401 vs cleartext all looked identical).

- testConnection now returns { ok, error } with the real error message
- add.tsx and [id].tsx dialogs show the error + target URL, plus a
  Tailscale/MagicDNS hint
- IP field keyboard: decimal-pad -> url, so tailnet hostnames can be
  typed (not just pasted)

Verified backend is healthy and reachable over tailnet (health 200,
port 4096 open in packet filter, cleartext present in shipped v0.2.0
APK), so the failure is client-side and was previously unobservable.

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

* fix(connect): normalize pasted IP/URL to avoid double scheme

Quick-connect blindly did `http://${ip}:${port}`. Pasting a full URL
(e.g. "http://100.108.64.76:4096", as the clipboard auto-paste offers)
produced "http://http://100.108.64.76:4096:4096" -> malformed URL ->
"Network request failed". This is the real tailnet connect failure:
typing a bare IP worked, pasting the displayed URL did not.

buildUrl now strips an existing http(s) scheme, drops any path, and
lifts a trailing :port out of the host field, so pasted full URLs,
host:port, and bare hosts all resolve to a single well-formed URL.

Reproduced and fixed on the Android emulator (paste full URL: fails
before, connects after).

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 04:44:13 -07:00
Ubuntu
1843a7c37e Initial commit: OpenCode mobile app (extracted from opencode monorepo) 2026-05-17 19:52:54 +00:00