diff --git a/packages/app/src/components/agent-input-area.tsx b/packages/app/src/components/agent-input-area.tsx index 703264c9c..7595db5cb 100644 --- a/packages/app/src/components/agent-input-area.tsx +++ b/packages/app/src/components/agent-input-area.tsx @@ -6,6 +6,7 @@ import { TextInputContentSizeChangeEventData, Image, Alert, + Platform, } from "react-native"; import { useState, useEffect, useRef } from "react"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; @@ -34,6 +35,11 @@ interface AgentInputAreaProps { const MIN_INPUT_HEIGHT = 40; const MAX_INPUT_HEIGHT = 160; const BASE_VERTICAL_PADDING = (FOOTER_HEIGHT - MIN_INPUT_HEIGHT) / 2; +// Android currently crashes inside ViewGroup.dispatchDraw when running Reanimated +// entering/exiting animations (see react-native-reanimated#8422), so guard them. +const SHOULD_DISABLE_ENTRY_EXIT_ANIMATIONS = Platform.OS === "android"; +const REALTIME_FADE_IN = SHOULD_DISABLE_ENTRY_EXIT_ANIMATIONS ? undefined : FadeIn.duration(250); +const REALTIME_FADE_OUT = SHOULD_DISABLE_ENTRY_EXIT_ANIMATIONS ? undefined : FadeOut.duration(250); export function AgentInputArea({ agentId }: AgentInputAreaProps) { const { theme } = useUnistyles(); @@ -268,8 +274,8 @@ export function AgentInputArea({ agentId }: AgentInputAreaProps) { {isRealtimeMode && ( diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index 178684a46..4250935a0 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -8,6 +8,7 @@ import { NativeScrollEvent, NativeSyntheticEvent, InteractionManager, + Platform, } from "react-native"; import Markdown from "react-native-markdown-display"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -66,6 +67,11 @@ export function AgentStreamView({ const isUserScrollingRef = useRef(false); const router = useRouter(); const { requestDirectoryListing, requestFilePreview } = useSession(); + // Keep entry/exit animations off on Android due to RN dispatchDraw crashes + // tracked in react-native-reanimated#8422. + const shouldDisableEntryExitAnimations = Platform.OS === "android"; + const scrollIndicatorFadeIn = shouldDisableEntryExitAnimations ? undefined : FadeIn.duration(200); + const scrollIndicatorFadeOut = shouldDisableEntryExitAnimations ? undefined : FadeOut.duration(200); useEffect(() => { hasScrolledInitially.current = false; @@ -252,6 +258,9 @@ export function AgentStreamView({ result={data.result} error={data.error} status={data.status as "executing" | "completed" | "failed"} + parsedEditEntries={data.parsedEdits} + parsedReadEntries={data.parsedReads} + parsedCommandDetails={data.parsedCommand ?? null} onOpenDetails={() => handleOpenToolCallDetails({ payload })} /> ); @@ -363,8 +372,8 @@ export function AgentStreamView({ {!isNearBottom && ( diff --git a/plan.md b/plan.md index bdf52a04a..5f1f5e754 100644 --- a/plan.md +++ b/plan.md @@ -41,4 +41,10 @@ - Hardened the assistant/thought timeline id generator so it now checks for existing ids before committing a suffix, ensuring new entries stay unique even if the stream shrinks, and added regression coverage in `test-idempotent-stream.ts` to prove assistant and reasoning ids remain unique after list pruning. Verified via `npx tsx test-idempotent-stream.ts`; note Test 1 still intentionally highlights state differences as before. - The warning points at the permission request cards rendered in the stream header; they’re reusing the same key so start the investigation there. - [x] Permission request cards rendered in the stream header emit duplicate key warnings because consecutive requests share the same `request.id`; derive a composite key so they stay unique per agent even when IDs collide or are missing. + - Switched the header map to key permission cards by `${agentId}:${request.id}` with a title/name/index fallback so React no longer sees duplicate keys even if providers reuse an id or omit it altogether. Ran `npm run typecheck --workspace=@voice-dev/app`. + +- [x] Investigate and fix: ERROR Your app just crashed. See the error below. + java.lang.NullPointerException: Attempt to read from field 'int android.view.View.mViewFlags' on a null object reference in method 'void android.view.ViewGroup.dispatchDraw(android.graphics.Canvas)' + android.view.ViewGroup.dispatchDraw(ViewGroup.java:4396) + - reproducible crash came from Reanimated `entering/exiting` transitions on Android (react-native-reanimated#8422), so we now disable those fade animations for AgentInputArea, GlobalFooter, and AgentStreamView when running on Android to prevent ViewGroup.dispatchDraw from touching a null child; verified with `npm run typecheck --workspace=@voice-dev/app`.