feat(mobile): revamped mobile experience (MVP)#1761
Conversation
Ports the mobile-only work from #1655 into a dedicated PR so the mobile app can ship independently of the desktop/backend changes. Highlights: - Task session view with plan status, questions, swipe-to-archive, haptics - Rich chat surfaces (MarkdownText, richer ToolMessage, voice recording) - QR scanner + auth flow refinements - Network status + offline banner, preferences store, archived tasks store - Syntax highlighting via highlight.js, meep sound cue - Hardened task API with retry/backoff No tests added yet (matches existing mobile app baseline). Typecheck passes for all changes in this PR; one unrelated pre-existing error remains in ConversationList.tsx on main. Generated-By: PostHog Code Task-Id: b2269cdf-fbbc-418e-ad9d-15418735e44a
- Replace 13 console.error calls with scoped logger (CLAUDE.md)
- Fix PlanStatusBar key collision risk: key={entry.content} → `${index}-${entry.content}`
- Extract duplicate formatRelativeTime → shared lib/format.ts
- Clear pollTicks in AppState.active handler to prevent per-session leak
Generated-By: PostHog Code
Task-Id: b2269cdf-fbbc-418e-ad9d-15418735e44a
Ported from #1655. Now that mobile work is active, the deny rules block iteration. Generated-By: PostHog Code Task-Id: b2269cdf-fbbc-418e-ad9d-15418735e44a
|
…, logger - api.ts: introduce HttpError class carrying response.status; update isRetryableError to duck-type on `.status` so 5xx errors actually retry (regex on statusText never matched "Internal Server Error"). All 10 throw sites migrated. - api.ts: replace stray console.error in createTask with scoped logger (missed in earlier sweep because api.ts wasn't in the original set). - SwipeableTaskItem: route props through propsRef so gesture callbacks read current values. Was latent only because FlatList keyExtractor remounts on isArchived change, but guards against future drift. Generated-By: PostHog Code Task-Id: b2269cdf-fbbc-418e-ad9d-15418735e44a
|
Thanks @greptile-apps — addressed all three in 09a417e: P1 · 5xx retry detection never fires ( P2 · Stray P2 · Stale closure in |
Prompt To Fix All With AIThis is a comment left during a code review.
Path: apps/mobile/src/features/tasks/stores/taskSessionStore.ts
Line: 114
Comment:
**`_handleEvent` is dead code**
`_handleEvent` is declared in the `TaskSessionStore` interface and implemented in the store, but a codebase-wide search finds it is never called — not by any component, hook, or internal store method. The polling path batches events directly via `set()`, making this method redundant.
Removing it satisfies the "no superfluous parts" rule and avoids confusion about whether there is an alternative, non-polling event ingestion path.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/mobile/src/features/chat/hooks/useVoiceRecording.ts
Line: 122-134
Comment:
**Transcribing state is unrecoverable if speech engine goes silent**
`stopRecording` resolves only when `ExpoSpeechRecognitionModule` fires a `result` (isFinal), `error`, or `end` event after `stop()`. If the underlying engine fails silently (observed on some Android devices when backgrounding mid-recognition), none of those events fire and the returned Promise never resolves.
The Composer's `handleMicPress` awaits this Promise, leaving the component permanently stuck in the "Transcribing…" visual state. Crucially, the long-press cancel path is gated on `isRecording`, so once `status === "transcribing"` there is no user-accessible escape.
Adding a timeout — e.g. `Promise.race([recognizerPromise, timeout(5000, null)])` — and calling `cleanup()` + `setStatus("idle")` on expiry would prevent the lock-out.
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: apps/mobile/src/features/tasks/stores/archivedTasksStore.ts
Line: 17-40
Comment:
**Archived task IDs accumulate indefinitely**
`archivedTasks` stores IDs keyed by timestamp and is persisted to `AsyncStorage`. There is no eviction path for tasks that have since been deleted from the backend. Over time (and especially for power users who archive-then-delete tasks regularly) the storage entry grows without bound.
Consider pruning entries whose IDs no longer appear in the task list, or capping the store to the N most recently archived IDs, before the data size becomes noticeable.
How can I resolve this? If you propose a fix, please make it concise.Reviews (2): Last reviewed commit: "fix(mobile): address Greptile review — r..." | Re-trigger Greptile |
- taskSessionStore: remove dead _handleEvent method + interface entry. Polling writes events directly; no caller exists. - useVoiceRecording: guard stopRecording with a 5s timeout. Some Android engines go silent when backgrounded mid-recognition and never fire result/error/end, which previously locked the UI on "Transcribing…" with no user escape. - archivedTasksStore: cap persisted archived IDs at 100 (evict oldest by timestamp) so AsyncStorage can't grow without bound. Generated-By: PostHog Code Task-Id: b2269cdf-fbbc-418e-ad9d-15418735e44a
|
Round 2 addressed in 5ed95b2:
Transcribing state unrecoverable (
|
Summary
Extracts the mobile-only work from #1655 into a dedicated PR so the mobile app can ship independently of the desktop/agent changes. The mobile app talks to PostHog HTTP APIs directly — zero cross-package coupling.
What's included
Tasks
TaskSessionViewwith plan status bar, question cards, swipe-to-archive (haptics + reanimated)archivedTasksStore,preferencesStoreapi.tshardened with exponential backoff + retry on 5xx/network errorsChat
MarkdownTextrenderer + richerToolMessage(tool-specific rendering per ACP tool kind)expo-speech-recognition)Onboarding & platform
QrScanModalfor dev/PAT login flowOfflineBanner+useNetworkStatus(@react-native-community/netinfo)syntax-highlightutil (highlight.js) for code blocksNew Expo deps:
expo-camera,expo-speech-recognition,@react-native-community/netinfo,highlight.jsScope boundaries
apps/mobile/**+pnpm-lock.yamlapps/code,packages/agent, or shared typesQuality pass
Ran
/code-review+/simplify. Fixes applied:console.error→ scopedlogger(CLAUDE.md compliance)PlanStatusBarkey collision fix (${index}-${entry.content})formatRelativeTimetolib/format.tspollTicks.clear()inAppState.activehandler (was leaking per-session tick counters across background/foreground cycles)SwipeableTaskItem.onPanResponderMove(removed typedAnimated.eventlistener that failed to compile; no perf delta sinceuseNativeDriver: false)Final review grade: A (MVP-ready).
Testing
pnpm --filter @posthog/mobile exec tsc --noEmit— clean for all files in this PR. One pre-existing error inConversationList.tsx:32onmainis unrelated and out of scope.Follow-ups (not in this PR)
ConversationList.tsxretryPressabletypetaskSessionStore+api.tsretry logicCreated with PostHog Code