Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
23046ad
fix(chat): stop recycled message rows from leaking state and stale he…
chrisnojima Jul 9, 2026
ec87bbe
feat(chat): re-add native LegendList for the message thread
chrisnojima Jul 9, 2026
8a016ed
fix(chat): keep native thread pinned to bottom through keyboard dismi…
chrisnojima Jul 17, 2026
3afb290
fix(chat): land search hits reliably via patched legend-list
chrisnojima Aug 11, 2026
1e87b7b
fix(chat): rework the legend-list scroll fixes after review
chrisnojima Aug 11, 2026
8008c0e
fix(chat): address second-round review of the legend-list patch
chrisnojima Aug 11, 2026
8ed65df
fix(chat): scroll to a search hit once, not on every index shift
chrisnojima Aug 11, 2026
c4c06c0
test(e2e): drive thread search hits on device
chrisnojima Aug 11, 2026
0c4fbbb
test(e2e): make the drag-after-hit case provoke a real page-in
chrisnojima Aug 11, 2026
ecdd823
test(e2e): make the thread-search flow addressable and honest
chrisnojima Aug 11, 2026
17f711e
test(e2e): provoke the page-in after the reader moves, not before
chrisnojima Aug 12, 2026
e069e37
test(e2e): drag until the hit is gone, rather than a fixed number of …
chrisnojima Aug 12, 2026
61d3820
test(e2e): stop retrying the thread-search flow
chrisnojima Aug 12, 2026
ffdbbbd
test(e2e): make retries opt-in per flow rather than the default
chrisnojima Aug 12, 2026
cba849e
test(e2e): let the reset escape a keyboard and a modal it could not b…
chrisnojima Aug 12, 2026
8096cbc
test(e2e): click the feed row, not the header avatar
chrisnojima Aug 12, 2026
4d6c08b
fix(chat): update legend-list patch, which had stopped reaching searc…
chrisnojima Aug 12, 2026
728df34
test(e2e): fix what review found in the harness, and read the hit cou…
chrisnojima Aug 12, 2026
1643e1e
test(e2e): make the reset's waits mean what they say
chrisnojima Aug 12, 2026
1892bbf
fix(chat): give the native thread the resets the desktop one already had
chrisnojima Aug 12, 2026
e26003f
test(e2e): cover the desktop search cases, and stop two checks from l…
chrisnojima Aug 12, 2026
1fdbc9e
test(e2e): stop the search flow from timing out as the thread grows
chrisnojima Aug 12, 2026
1550f44
test(e2e): point the search flows at a named conversation
chrisnojima Aug 12, 2026
f2f7e0e
fix(chat): drop the library change we could not show we needed
chrisnojima Aug 12, 2026
c318549
fix(chat): replace the borrowed-room scroll fix with re-issuing the s…
chrisnojima Aug 12, 2026
ed9e153
fix(chat): apply what the reviewers found
chrisnojima Aug 13, 2026
bd471f2
test(e2e): make the desktop search flow able to fail
chrisnojima Aug 13, 2026
16f17a6
fix(chat): one centred-scroll hook for both platforms
chrisnojima Aug 13, 2026
f518163
fix(chat): open a thread on its newest message
chrisnojima Aug 13, 2026
611ff6b
test(e2e): check that opening a thread lands on its newest message
chrisnojima Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shared/chat/conversation/header-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const HeaderAreaRight = (props: HeaderConversationProps) => {
noShrink={true}
style={Kb.Styles.collapseStyles([styles.headerRight, {opacity: pendingWaiting ? 0 : 1}])}
>
<Kb.Icon type="iconfont-search" onClick={onToggleThreadSearch} />
<Kb.Icon type="iconfont-search" onClick={onToggleThreadSearch} testID={TestIDs.CHAT_HEADER_SEARCH_BUTTON} />
<Kb.Icon type="iconfont-info" onClick={onShowInfoPanel} testID={TestIDs.CHAT_HEADER_INFO_BUTTON} />
</Kb.Box2>
)
Expand Down
655 changes: 159 additions & 496 deletions shared/chat/conversation/list-area/index.tsx

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion shared/chat/conversation/messages/special-top-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import {useConversationParticipantsSelector} from '../data-hooks'
import * as FS from '@/constants/fs'
import {useCurrentUserState} from '@/stores/current-user'
import * as TestIDs from '@/tests/e2e/shared/test-ids'

const ErrorMessage = () => {
const styles = useStyles()
Expand Down Expand Up @@ -153,7 +154,13 @@ function SpecialTopMessage() {
}

return (
<Kb.Box2 direction="vertical" fullWidth={true} style={styles.container}>
<Kb.Box2
direction="vertical"
fullWidth={true}
style={styles.container}
testID={TestIDs.CHAT_THREAD_TOP}
collapsable={false}
>
{hasLoadedEver && loadMoreType === 'noMoreToLoad' && showRetentionNotice && <RetentionNotice />}
<Kb.Box2 direction="vertical" style={styles.spacer} />
{hasOlderResetConversation && <ProfileResetNotice />}
Expand Down
17 changes: 13 additions & 4 deletions shared/chat/conversation/messages/wrapper/long-pressable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Props = {
import {useConversationThreadToggleSearch} from '../../../thread-context'
import Swipeable, {type SwipeableMethods} from '@/common-adapters/swipeable-row'
import {ThreadRefsContext} from '@/chat/conversation/normal/context'
import {useAdaptiveRender} from '@legendapp/list/react-native'

function ReplyIcon({progress}: {progress: Animated.Value}) {
const styles = useStyles()
Expand All @@ -28,16 +29,23 @@ function ReplyIcon({progress}: {progress: Animated.Value}) {
}

function LongPressable(props: Props & {ref?: React.Ref<Kb.MeasureRef>}) {
if (!isMobile) {
return <Kb.Box2 direction="horizontal" fullWidth={true} {...props} />
}
return <LongPressableMobile {...props} />
}

function LongPressableMobile(props: Props & {ref?: React.Ref<Kb.MeasureRef>}) {
const styles = useStyles()
const toggleThreadSearch = useConversationThreadToggleSearch()
const setReplyTo = InputState.useConversationInputDispatch(s => s.setReplyTo)
const ordinal = useOrdinal()
const {focusInput} = React.useContext(ThreadRefsContext)
const swipeRef = React.useRef<SwipeableMethods | null>(null)

if (!isMobile) {
return <Kb.Box2 direction="horizontal" fullWidth={true} {...props} />
}
// Velocity-driven signal from LegendList: during fast scroll it flips to "light". We keep the
// Swipeable mounted (toggling its tree would remount children and flash images) and instead just
// disable its pan handlers in light mode, shedding the per-row touch evaluation during the fling.
const adaptiveMode = useAdaptiveRender()

const {children, onLongPress, style} = props

Expand Down Expand Up @@ -68,6 +76,7 @@ function LongPressable(props: Props & {ref?: React.Ref<Kb.MeasureRef>}) {
return (
<Swipeable
ref={swipeRef}
enabled={adaptiveMode !== 'light'}
renderRightActions={makeAction}
onSwipeableWillOpen={onSwipeableWillOpen}
>
Expand Down
10 changes: 5 additions & 5 deletions shared/chat/conversation/messages/wrapper/sent.native.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type * as React from 'react'
import Animated, {FadeInDown} from 'react-native-reanimated'

// Slide-up + fade for a message you just sent. The thread list is an inverted
// FlatList (cells are flipped with scaleY: -1), so FadeInDown renders on screen
// as sliding up from below. Runs entirely on the UI thread with no re-renders.
// The entering animation only plays when this Animated.View MOUNTS — callers must
// key it per message (recycled containers reuse instances).
// Slide-up + fade for a message you just sent. The thread list (LegendList) is
// NOT inverted, so FadeInDown (enters from 25px below, sliding up into place)
// reads as the row rising from the input bar. Runs entirely on the UI thread
// with no re-renders. The entering animation only plays when this Animated.View
// MOUNTS — callers must key it per message (recycled containers reuse instances).
export function Sent(p: {children: React.ReactNode}) {
return (
<Animated.View entering={FadeInDown.duration(200)} style={styles.container}>
Expand Down
9 changes: 8 additions & 1 deletion shared/chat/conversation/messages/wrapper/wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {emptyParticipantInfo} from '../../data-hooks'
import {useInboxMetadataState} from '@/chat/inbox/metadata'
import type {ConversationInputState} from '../../input-area/input-state'
import {useChatTeamMemberRole} from '../../team-hooks'
import * as TestIDs from '@/tests/e2e/shared/test-ids'

type AccountsInfoMap = ReadonlyMap<T.RPCChat.MessageID, T.Chat.ChatRequestInfo | T.Chat.ChatPaymentInfo>
type PaymentStatusMap = ReadonlyMap<T.Wallets.PaymentID, T.Chat.ChatPaymentInfo>
Expand Down Expand Up @@ -1018,7 +1019,13 @@ export function WrapperMessage(p: WrapperMessageProps) {
const messageContext = {isHighlighted: showCenteredHighlight, ordinal}

const row = (
<Kb.Box2 direction="vertical" relative={true} fullWidth={true}>
<Kb.Box2
direction="vertical"
relative={true}
fullWidth={true}
collapsable={false}
testID={showCenteredHighlight ? TestIDs.CHAT_SEARCH_HIT : undefined}
>
<AuthorHeader
author={author}
botAlias={botAlias}
Expand Down
40 changes: 36 additions & 4 deletions shared/chat/conversation/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from './thread-context'
import {useThreadSearchRoute} from './thread-search-route'
import {ThreadSearchOverlayContext} from './thread-search-overlay-context'
import * as TestIDs from '@/tests/e2e/shared/test-ids'

type OwnProps = {style?: Kb.Styles.StylesCrossPlatform}
type CommonProps = OwnProps & {
Expand Down Expand Up @@ -434,8 +435,15 @@ const ThreadSearchDesktopInner = function ThreadSearchDesktopInner(p: CommonProp
</Kb.Box2>
<Kb.Box2 direction="horizontal" gap="tiny" noShrink={true}>
{inProgress && <Kb.ProgressIndicator style={styles.progress} />}
{/* collapsable={false}: Android view flattening would drop this testID'd wrapper and
leave the count unreadable to the e2e suite. */}
{hasResults && (
<Kb.Box2 direction="horizontal" gap="tiny">
<Kb.Box2
direction="horizontal"
gap="tiny"
collapsable={false}
testID={TestIDs.CHAT_THREAD_SEARCH_COUNT}
>
<Kb.Text type="BodySmall" style={styles.results}>
{noResults ? 'No results' : `${selectedIndex + 1} of ${hits.length}`}
</Kb.Text>
Expand Down Expand Up @@ -506,13 +514,28 @@ const ThreadSearchMobileInner = function ThreadSearchMobileInner(p: CommonProps)
return (
<Kb.Box2 direction="vertical" fullWidth={true} style={styles.mobileContainer} onLayout={onLayout}>
<Kb.Box2 direction="horizontal" fullWidth={true} justifyContent="space-between" padding="tiny" style={styles.outerContainer} gap="tiny">
<Kb.Box2 direction="horizontal" centerChildren={true} noShrink={true}>
<Kb.Box2
direction="horizontal"
centerChildren={true}
noShrink={true}
collapsable={false}
testID={TestIDs.CHAT_THREAD_SEARCH_CANCEL}
>
<Kb.Text type="BodySemibold" style={styles.done} onClick={onToggleThreadSearch}>
Cancel
</Kb.Text>
</Kb.Box2>
<Kb.Box2 direction="horizontal" justifyContent="space-between" style={styles.inputContainer}>
<Kb.Box2 direction="horizontal" gap="xtiny" flex={1} centerChildren={true}>
{/* collapsable={false}: keep this testID'd wrapper (and the EditText under it) as a real
view on Android, where view flattening would otherwise render it as an empty leaf. */}
<Kb.Box2
direction="horizontal"
gap="xtiny"
flex={1}
centerChildren={true}
collapsable={false}
testID={TestIDs.CHAT_THREAD_SEARCH_INPUT}
>
<Kb.Input3
ref={inputRef}
autoFocus={false}
Expand All @@ -527,8 +550,15 @@ const ThreadSearchMobileInner = function ThreadSearchMobileInner(p: CommonProps)
</Kb.Box2>
<Kb.Box2 direction="horizontal" gap="tiny" noShrink={true}>
{inProgress && <Kb.ProgressIndicator style={styles.progress} />}
{/* collapsable={false}: Android view flattening would drop this testID'd wrapper and
leave the count unreadable to the e2e suite. */}
{hasResults && (
<Kb.Box2 direction="horizontal" gap="tiny">
<Kb.Box2
direction="horizontal"
gap="tiny"
collapsable={false}
testID={TestIDs.CHAT_THREAD_SEARCH_COUNT}
>
<Kb.Text type="BodySmall" style={styles.results}>
{status === 'done' && numHits === 0 ? 'No results' : `${selectedIndex + 1} of ${numHits}`}
</Kb.Text>
Expand All @@ -541,11 +571,13 @@ const ThreadSearchMobileInner = function ThreadSearchMobileInner(p: CommonProps)
color={numHits > 0 ? theme.blue : theme.black_50}
onClick={onUp}
type="iconfont-arrow-up"
testID={TestIDs.CHAT_THREAD_SEARCH_PREV}
/>
<Kb.Icon
color={numHits > 0 ? theme.blue : theme.black_50}
onClick={onDown}
type="iconfont-arrow-down"
testID={TestIDs.CHAT_THREAD_SEARCH_NEXT}
/>
</Kb.Box2>
</Kb.Box2>
Expand Down
8 changes: 7 additions & 1 deletion shared/chat/inbox-and-conversation-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {navToPath} from '@/constants/fs'
import {showConversationInfoPanel, toggleConversationThreadSearch} from '@/chat/conversation/thread-context'
import {muteConversation} from '@/chat/conversation/status-actions'
import AccountSwitchHeaderAvatar from '@/router-v2/account-switch-header-avatar'
import * as TestIDs from '@/tests/e2e/shared/test-ids'

const emptyMeta = Chat.makeConversationMeta()
const emptyParticipantInfo = Chat.uiParticipantsToParticipantInfo([])
Expand Down Expand Up @@ -245,7 +246,12 @@ const Header = () => {
direction="vertical"
tooltip={`Search in this chat (${C.shortcutSymbol}F)`}
>
<Kb.Icon style={styles.clickable} type="iconfont-search" onClick={onToggleThreadSearch} />
<Kb.Icon
style={styles.clickable}
type="iconfont-search"
onClick={onToggleThreadSearch}
testID={TestIDs.CHAT_HEADER_SEARCH_BUTTON}
/>
</Kb.Box2>
<Kb.Box2
className="tooltip-left"
Expand Down
9 changes: 8 additions & 1 deletion shared/chat/inbox/row/big-team-channel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type * as React from 'react'
import * as Kb from '@/common-adapters'
import * as RowSizes from './sizes'
import * as T from '@/constants/types'
import * as TestIDs from '@/tests/e2e/shared/test-ids'
import {useInboxRowBig} from '@/chat/inbox/rows-state'
type Props = {
conversationIDKey: string
Expand Down Expand Up @@ -92,7 +93,13 @@ const BigTeamChannel = (props: Props) => {
) : null

return (
<Kb.ClickableBox direction="vertical" fullWidth={true} onClick={onSelectConversation} style={styles.container}>
<Kb.ClickableBox
direction="vertical"
fullWidth={true}
onClick={onSelectConversation}
style={styles.container}
testID={TestIDs.CHAT_INBOX_CHANNEL_ROW}
>
<Kb.Box2 direction="horizontal" fullHeight={true} style={styles.rowContainer}>
<Kb.Box2
className="hover_background_color_blueGreyDark"
Expand Down
2 changes: 1 addition & 1 deletion shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"@gorhom/bottom-sheet": "5.2.14",
"@gorhom/portal": "1.0.14",
"@khanacademy/simple-markdown": "3.0.0",
"@legendapp/list": "3.3.4",
"@legendapp/list": "3.3.5",
"@msgpack/msgpack": "3.1.3",
"@react-native-community/netinfo": "12.0.1",
"@react-native-masked-view/masked-view": "0.3.2",
Expand Down
Loading