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`.