diff --git a/packages/app/e2e/mobile/agent-device/native-user-message-preview-preconditions.md b/packages/app/e2e/mobile/agent-device/native-user-message-preview-preconditions.md new file mode 100644 index 0000000000..9985a3f544 --- /dev/null +++ b/packages/app/e2e/mobile/agent-device/native-user-message-preview-preconditions.md @@ -0,0 +1,14 @@ +# Native user message preview + +Open a chat with a user message containing 16 newline-separated lines of +`Preview line N`, with N from 1 to 16. Scroll the message into view on a +phone-sized Android emulator or iOS simulator. Its text must exceed the 240-point preview cap, +while its expanded bubble and toggle fit in the chat viewport. Close any other +expanded messages so the toggle labels identify this message. + +Replay `native-user-message-preview.android.ad` or +`native-user-message-preview.ios.ad` through the existing mobile runner or +agent-device. It checks that a clipped message offers Show more and +that two expand/collapse round trips keep both controls reachable. Before the +fix, the first Show more assertion fails because native layout reports the +capped height as the full content height. diff --git a/packages/app/e2e/mobile/agent-device/native-user-message-preview.android.ad b/packages/app/e2e/mobile/agent-device/native-user-message-preview.android.ad new file mode 100644 index 0000000000..45630217bb --- /dev/null +++ b/packages/app/e2e/mobile/agent-device/native-user-message-preview.android.ad @@ -0,0 +1,14 @@ +context platform=android timeout=60000 retries=0 +env APP_ID=sh.paseo.debug + +open "${APP_ID}" +wait "role=\"button\" label=\"Show more\"" 10000 +press "role=\"button\" label=\"Show more\"" +wait "role=\"button\" label=\"Show less\"" 10000 +press "role=\"button\" label=\"Show less\"" +wait "role=\"button\" label=\"Show more\"" 10000 +press "role=\"button\" label=\"Show more\"" +wait "role=\"button\" label=\"Show less\"" 10000 +press "role=\"button\" label=\"Show less\"" +wait "role=\"button\" label=\"Show more\"" 10000 +close diff --git a/packages/app/e2e/mobile/agent-device/native-user-message-preview.ios.ad b/packages/app/e2e/mobile/agent-device/native-user-message-preview.ios.ad new file mode 100644 index 0000000000..d19d7dd8aa --- /dev/null +++ b/packages/app/e2e/mobile/agent-device/native-user-message-preview.ios.ad @@ -0,0 +1,14 @@ +context platform=ios timeout=60000 retries=0 +env APP_ID=sh.paseo.debug + +open "${APP_ID}" +wait "role=\"button\" label=\"Show more\"" 10000 +press "role=\"button\" label=\"Show more\"" +wait "role=\"button\" label=\"Show less\"" 10000 +press "role=\"button\" label=\"Show less\"" +wait "role=\"button\" label=\"Show more\"" 10000 +press "role=\"button\" label=\"Show more\"" +wait "role=\"button\" label=\"Show less\"" 10000 +press "role=\"button\" label=\"Show less\"" +wait "role=\"button\" label=\"Show more\"" 10000 +close diff --git a/packages/app/src/components/user-message/index.tsx b/packages/app/src/components/user-message/index.tsx index ba383eb0d3..5f5eef4ba6 100644 --- a/packages/app/src/components/user-message/index.tsx +++ b/packages/app/src/components/user-message/index.tsx @@ -1,5 +1,5 @@ import { useState, useCallback, useMemo, type ReactNode } from "react"; -import { View, type LayoutChangeEvent } from "react-native"; +import { View, type LayoutChangeEvent, type ViewStyle } from "react-native"; import { StyleSheet } from "react-native-unistyles"; import { useTranslation } from "react-i18next"; import type { AgentMessage } from "@getpaseo/protocol/agent-message"; @@ -8,23 +8,29 @@ import { EdgeFade } from "@/components/ui/edge-fade"; import { useProviderIcon } from "@/components/provider-icons"; import { useSessionStore } from "@/stores/session-store"; import { navigateToAgent } from "@/utils/navigate-to-agent"; +import { isNative } from "@/constants/platform"; const PREVIEW_HEIGHT = 240; +// Native View still clips with "scroll", but Yoga measures its children without +// the height cap. "hidden" caps the measured body too, hiding the expand control. +const previewStyle: ViewStyle = { + maxHeight: PREVIEW_HEIGHT, + overflow: isNative ? "scroll" : "hidden", +}; export function MessagePreview({ children }: { children: ReactNode }) { const { t } = useTranslation(); const [expanded, setExpanded] = useState(false); - const [height, setHeight] = useState(0); - const overflowing = height > PREVIEW_HEIGHT; + const [overflowing, setOverflowing] = useState(false); const measure = useCallback( - (event: LayoutChangeEvent) => setHeight(event.nativeEvent.layout.height), + (event: LayoutChangeEvent) => setOverflowing(event.nativeEvent.layout.height > PREVIEW_HEIGHT), [], ); const toggle = useCallback(() => setExpanded((value) => !value), []); const accessibilityState = useMemo(() => ({ expanded }), [expanded]); return ( <> - + {children} @@ -83,7 +89,6 @@ export function MessageSender({ } const styles = StyleSheet.create((theme) => ({ - preview: { maxHeight: PREVIEW_HEIGHT, overflow: "hidden" }, body: { flexShrink: 0 }, sender: { paddingRight: 0, borderRightWidth: 0 }, toggle: { alignSelf: "flex-start", paddingHorizontal: 0, minHeight: 0, borderWidth: 0 },