From befc3d5663dee4f9c9b092da1338c7f58a09e006 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 28 Oct 2026 13:46:43 +0700 Subject: [PATCH] fix(app): restore expansion of long native user messages (#8089) Native hidden overflow capped the measured content at the preview height, so long messages never offered Show more. Use the native scroll layout mode on the existing View to measure full content while preserving clipping, and store only whether it overflows. --- ...native-user-message-preview-preconditions.md | 14 ++++++++++++++ .../native-user-message-preview.android.ad | 14 ++++++++++++++ .../native-user-message-preview.ios.ad | 14 ++++++++++++++ .../app/src/components/user-message/index.tsx | 17 +++++++++++------ 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 packages/app/e2e/mobile/agent-device/native-user-message-preview-preconditions.md create mode 100644 packages/app/e2e/mobile/agent-device/native-user-message-preview.android.ad create mode 100644 packages/app/e2e/mobile/agent-device/native-user-message-preview.ios.ad 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 },