From 868a867ce6cd2e67bcb0e03be1441447c2d3385e Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Tue, 8 Sep 2026 15:53:21 -0400 Subject: [PATCH 1/3] feat(chat): jump to the unread line with a Catch up pill When the orange unread line sits above the viewport, show an orange "Catch up" pill at the top right of the thread. Tapping it centers on the oldest unread message using the existing search-jump path with the highlight turned off, then dismisses itself for that unread line. Ordinals are monotonic and the unreadline is a MessageID carried as one, so comparing it against the oldest visible ordinal covers both "scrolled off above" and "not loaded at all" with one predicate. Desktop's LegendList had no viewability wiring, so it gains onViewableItemsChanged to report the same signal mobile already tracks. Also fixes the orange line never rendering on mobile at all. React Native's ItemSeparatorComponent receives {highlighted, leadingItem} and never trailingItem, which the separator required, so it bailed out every time. The native list casts FlatList to Record, which hid the mismatch from tsc. NativeSeparator adapts the prop: the separator belongs to cell i and sits between items i and i+1, and this list reverses its data, so item i is the newer of the pair -- exactly what desktop passes as trailingItem. --- .../conversation/list-area/catch-up.test.tsx | 118 ++++++++++++++++ .../chat/conversation/list-area/catch-up.tsx | 126 ++++++++++++++++++ shared/chat/conversation/list-area/index.tsx | 23 +++- .../conversation/messages/separator.test.tsx | 75 +++++++++++ .../chat/conversation/messages/separator.tsx | 9 ++ shared/tsconfig.native.json | 1 + 6 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 shared/chat/conversation/list-area/catch-up.test.tsx create mode 100644 shared/chat/conversation/list-area/catch-up.tsx create mode 100644 shared/chat/conversation/messages/separator.test.tsx diff --git a/shared/chat/conversation/list-area/catch-up.test.tsx b/shared/chat/conversation/list-area/catch-up.test.tsx new file mode 100644 index 000000000000..c314e0ad462f --- /dev/null +++ b/shared/chat/conversation/list-area/catch-up.test.tsx @@ -0,0 +1,118 @@ +/** @jest-environment jsdom */ +/// +import * as React from 'react' +import * as T from '@/constants/types' +import {act, cleanup, render} from '@testing-library/react' +import {OrangeLineContext} from '../orange-line-context' +import {shouldShowCatchUp, useCatchUp} from './catch-up' + +const ord = T.Chat.numberToOrdinal + +// The orange line sits at ordinal 10, the viewport starts at 50: the unread boundary is off +// screen above. +const scrolledPastTheOrangeLine = { + dismissedOrdinal: ord(0), + loaded: true, + oldestVisibleOrdinal: ord(50), + orangeLineOrdinal: ord(10), + threadSearchVisible: false, +} + +test('shows when the orange line is older than the oldest visible message', () => { + expect(shouldShowCatchUp(scrolledPastTheOrangeLine)).toBe(true) +}) + +test('hides when there is no orange line at all', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, orangeLineOrdinal: ord(0)})).toBe(false) +}) + +test('hides when the orange line is the oldest visible message', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, orangeLineOrdinal: ord(50)})).toBe(false) +}) + +test('hides before the list has reported what it can see', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, oldestVisibleOrdinal: undefined})).toBe(false) +}) + +test('hides until the thread has loaded', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, loaded: false})).toBe(false) +}) + +test('hides while thread search is open', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, threadSearchVisible: true})).toBe(false) +}) + +test('stays hidden once dismissed for this orange line', () => { + expect(shouldShowCatchUp({...scrolledPastTheOrangeLine, dismissedOrdinal: ord(10)})).toBe(false) +}) + +test('comes back when a newer orange line replaces the dismissed one', () => { + expect( + shouldShowCatchUp({...scrolledPastTheOrangeLine, dismissedOrdinal: ord(10), orangeLineOrdinal: ord(20)}) + ).toBe(true) +}) + +const mockCenterOnMessage = jest.fn() +let mockRouteParams: {threadSearch?: {query?: string}} | undefined + +jest.mock('../center-context', () => ({ + useConversationCenterActions: () => ({centerOnMessage: mockCenterOnMessage}), +})) +jest.mock('../thread-search-route', () => ({useChatThreadRouteParams: () => mockRouteParams})) + +let seen: ReturnType | undefined + +const Probe = (p: {loaded: boolean}) => { + const catchUp = useCatchUp({loaded: p.loaded}) + // captured in an effect: assigning module state during render is a side effect the lint rejects + React.useEffect(() => { + seen = catchUp + }) + return null +} + +const Tree = (p: {loaded?: boolean; orangeLineOrdinal: T.Chat.Ordinal}) => ( + + + +) + +describe('useCatchUp', () => { + beforeEach(() => { + mockCenterOnMessage.mockClear() + mockRouteParams = undefined + seen = undefined + }) + afterEach(cleanup) + + test('stays hidden until the list reports a viewport above the orange line', () => { + render() + expect(seen?.showCatchUp).toBe(false) + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + expect(seen?.showCatchUp).toBe(true) + }) + + test('tapping centers on the orange line with no highlight', () => { + render() + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + act(() => { + seen?.onCatchUp() + }) + expect(mockCenterOnMessage).toHaveBeenCalledWith(T.Chat.numberToMessageID(10), 'none') + }) + + test('tapping dismisses the pill even though the viewport has not moved', () => { + render() + act(() => { + seen?.onViewableOrdinalsChanged(ord(50)) + }) + act(() => { + seen?.onCatchUp() + }) + expect(seen?.showCatchUp).toBe(false) + }) +}) diff --git a/shared/chat/conversation/list-area/catch-up.tsx b/shared/chat/conversation/list-area/catch-up.tsx new file mode 100644 index 000000000000..c66a8c28252c --- /dev/null +++ b/shared/chat/conversation/list-area/catch-up.tsx @@ -0,0 +1,126 @@ +import * as Kb from '@/common-adapters' +import * as React from 'react' +import * as T from '@/constants/types' +import {OrangeLineContext} from '../orange-line-context' +import {useChatThreadRouteParams} from '../thread-search-route' +import {useConversationCenterActions} from '../center-context' + +const noOrdinal = T.Chat.numberToOrdinal(0) + +// The unreadline arrives as a MessageID and is carried as an Ordinal, which is sound because server +// messages get ordinal === messageID. Centering wants it back as a MessageID. +const orangeLineToMessageID = (ordinal: T.Chat.Ordinal) => + T.Chat.numberToMessageID(T.Chat.ordinalToNumber(ordinal)) + +// Ordinals are monotonic and the orange line is a MessageID coerced to one, so a plain comparison +// tells us the unread boundary is above the viewport even when that message isn't loaded at all. +export const shouldShowCatchUp = (p: { + dismissedOrdinal: T.Chat.Ordinal + loaded: boolean + oldestVisibleOrdinal: T.Chat.Ordinal | undefined + orangeLineOrdinal: T.Chat.Ordinal + threadSearchVisible: boolean +}) => { + const {dismissedOrdinal, loaded, oldestVisibleOrdinal, orangeLineOrdinal, threadSearchVisible} = p + if (!loaded || threadSearchVisible) { + return false + } + if (oldestVisibleOrdinal === undefined || !T.Chat.ordinalToNumber(orangeLineOrdinal)) { + return false + } + // Dismissal is remembered per orange line, not per visit, so marking an older message unread + // re-arms the pill for the new boundary. + if (dismissedOrdinal === orangeLineOrdinal) { + return false + } + return orangeLineOrdinal < oldestVisibleOrdinal +} + +// The viewport moves on every scroll frame, so the viewable ordinal lives in a ref and only the +// show/hide answer is state: an unchanged answer bails out of re-rendering the message list. +export const useCatchUp = (p: {loaded: boolean}) => { + const {loaded} = p + const orangeLineOrdinal = React.useContext(OrangeLineContext) + const routeParams = useChatThreadRouteParams() + const threadSearchVisible = !!routeParams?.threadSearch + const {centerOnMessage} = useConversationCenterActions() + const [dismissedOrdinal, setDismissedOrdinal] = React.useState(noOrdinal) + const [showCatchUp, setShowCatchUp] = React.useState(false) + const oldestVisibleOrdinalRef = React.useRef(undefined) + + const recompute = React.useEffectEvent(() => { + setShowCatchUp( + shouldShowCatchUp({ + dismissedOrdinal, + loaded, + oldestVisibleOrdinal: oldestVisibleOrdinalRef.current, + orangeLineOrdinal, + threadSearchVisible, + }) + ) + }) + + React.useEffect(() => { + recompute() + }, [dismissedOrdinal, loaded, orangeLineOrdinal, threadSearchVisible]) + + // Held in state rather than a useCallback so the identity is stable for the lists, which capture + // this once inside their own scroll handlers. + const [onViewableOrdinalsChanged] = React.useState( + () => (oldestVisibleOrdinal?: T.Chat.Ordinal) => { + oldestVisibleOrdinalRef.current = oldestVisibleOrdinal + recompute() + } + ) + + const onCatchUp = React.useCallback(() => { + setDismissedOrdinal(orangeLineOrdinal) + setShowCatchUp(false) + centerOnMessage(orangeLineToMessageID(orangeLineOrdinal), 'none') + }, [centerOnMessage, orangeLineOrdinal]) + + return {onCatchUp, onViewableOrdinalsChanged, showCatchUp} +} + +// Orange to match the unread line itself, so the pill reads as "that line, up there". +export const CatchUp = (p: {onClick: () => void}) => { + const {onClick} = p + const styles = useStyles() + const theme = Kb.Styles.useTheme() + return ( + + + + + Catch up + + + + ) +} + +const useStyles = Kb.Styles.createStyleHook( + theme => + ({ + container: { + position: 'absolute', + right: Kb.Styles.globalMargins.tiny, + top: Kb.Styles.globalMargins.tiny, + }, + label: {color: theme.whiteOrWhite}, + pill: { + backgroundColor: theme.orange, + borderRadius: 100, + paddingBottom: Kb.Styles.globalMargins.xtiny, + paddingLeft: Kb.Styles.globalMargins.tiny, + paddingRight: Kb.Styles.globalMargins.tiny, + paddingTop: Kb.Styles.globalMargins.xtiny, + }, + }) as const +) diff --git a/shared/chat/conversation/list-area/index.tsx b/shared/chat/conversation/list-area/index.tsx index 7800f4eb543d..cd67d88f843b 100644 --- a/shared/chat/conversation/list-area/index.tsx +++ b/shared/chat/conversation/list-area/index.tsx @@ -3,7 +3,7 @@ import * as Kb from '@/common-adapters' import * as React from 'react' import * as T from '@/constants/types' import * as TestIDs from '@/tests/e2e/shared/test-ids' -import Separator from '../messages/separator' +import Separator, {NativeSeparator} from '../messages/separator' import SpecialBottomMessage from '../messages/special-bottom-message' import SpecialTopMessage from '../messages/special-top-message' import {MessageRow} from '../messages/wrapper' @@ -20,6 +20,7 @@ import { useConversationThreadSelector, useConversationThreadStore, } from '../thread-context' +import {CatchUp, useCatchUp} from './catch-up' import {useJumpToRecent} from './jump-to-recent' import {useThreadLoadStatusOptionsGetter} from '../thread-load-status-context' import {getMessageRowType, getMessageShowUsername} from '../messages/row-metadata' @@ -538,6 +539,15 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { const jumpToRecent = useJumpToRecent(scrollToBottom, messageOrdinals.length) + const {onCatchUp, onViewableOrdinalsChanged, showCatchUp} = useCatchUp({loaded}) + // Data runs oldest-first here, so the first viewable row is the oldest one on screen. + const onViewableItemsChanged = React.useCallback( + (info: {viewableItems: ReadonlyArray<{item: T.Chat.Ordinal}>}) => { + onViewableOrdinalsChanged(info.viewableItems.at(0)?.item) + }, + [onViewableOrdinalsChanged] + ) + const {focusInput} = React.useContext(ThreadRefsContext) const handleListClick = (ev: React.MouseEvent) => { const target = ev.target as { @@ -657,8 +667,10 @@ const DesktopThreadWrapper = function DesktopThreadWrapper() { onStartReached={onStartReached} onStartReachedThreshold={2} onEndReached={onEndReached} + onViewableItemsChanged={onViewableItemsChanged} /> {jumpToRecent} + {showCatchUp && } ) @@ -947,6 +959,8 @@ const NativeConversationList = function NativeConversationList() { const jumpToRecent = useJumpToRecent(scrollToBottom, messageOrdinals.length) + const {onCatchUp, onViewableOrdinalsChanged, showCatchUp} = useCatchUp({loaded}) + // When keyboard is open, maintainVisibleContentPosition adjusts contentOffset by the new // message height when a message is added, undoing the scrollToBottom from onSubmit. // Defer the re-scroll past the native MPV adjustment (which runs on the UI thread after @@ -1040,13 +1054,15 @@ const NativeConversationList = function NativeConversationList() { const onViewableItemsChanged = useNativeSafeOnViewableItemsChanged(onEndReached, messageOrdinals.length) const [onViewableItemsChangedNative] = React.useState( - () => (info: {viewableItems: Array<{index: number | null}>}) => { + () => (info: {viewableItems: Array<{index: number | null; item: T.Chat.Ordinal}>}) => { onViewableItemsChanged.current(info) const first = info.viewableItems.at(0)?.index const last = info.viewableItems.at(-1)?.index vFirstRef.current = first vLastRef.current = last correctCenter(first, last) + // The list is inverted and its data reversed, so the last viewable row is the oldest. + onViewableOrdinalsChanged(info.viewableItems.at(-1)?.item) } ) @@ -1086,7 +1102,7 @@ const NativeConversationList = function NativeConversationList() { estimatedItemSize={72} ListHeaderComponent={SpecialBottomMessage} ListFooterComponent={SpecialTopMessage} - ItemSeparatorComponent={Separator} + ItemSeparatorComponent={NativeSeparator} overScrollMode="never" contentContainerStyle={nativeContentContainerStyle} data={messageOrdinals} @@ -1115,6 +1131,7 @@ const NativeConversationList = function NativeConversationList() { {jumpToRecent} )} + {showCatchUp && } diff --git a/shared/chat/conversation/messages/separator.test.tsx b/shared/chat/conversation/messages/separator.test.tsx new file mode 100644 index 000000000000..150cf83e24c7 --- /dev/null +++ b/shared/chat/conversation/messages/separator.test.tsx @@ -0,0 +1,75 @@ +/** @jest-environment jsdom */ +/// +import type * as React from 'react' +import * as T from '@/constants/types' +import * as Chat from '@/constants/chat' +import {cleanup, render} from '@testing-library/react' +import {OrangeLineContext} from '../orange-line-context' + +const ord = T.Chat.numberToOrdinal +const older = ord(10) +const newer = ord(20) + +// The thread the separator reads: two messages, the unread boundary sitting between them, so the +// separator above `newer` is the one that must draw the orange line. +type SeparatorData = {orangeLineAbove: boolean; orangeTime: string; ordinal: T.Chat.Ordinal} +let mockThreadState: unknown + +jest.mock('../thread-context', () => ({ + // read via useContext but only consulted for the desktop timestamp label, which these cases + // don't exercise + ShownUsernameCacheContext: {}, + useConversationThreadSelector: (sel: (s: unknown) => SeparatorData): SeparatorData => + sel(mockThreadState), +})) +jest.mock('@/stores/current-user', () => ({useCurrentUserState: () => 'testuser'})) + +import Separator, {NativeSeparator} from './separator' + +const Tree = (p: {children: React.ReactNode}) => ( + {p.children} +) + +// The line is a 1px bar painted with the orange theme token, so its presence is what proves the +// separator drew an unread boundary rather than merely rendering something. +const drewOrangeLine = (container: HTMLElement) => container.innerHTML.includes('var(--color-orange)') + +beforeEach(() => { + mockThreadState = { + messageMap: new Map([ + [older, Chat.makeMessageText({ordinal: older})], + [newer, Chat.makeMessageText({ordinal: newer})], + ]), + messageOrdinals: [older, newer], + } +}) +afterEach(cleanup) + +test('draws the orange line for the message below the unread boundary', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(true) +}) + +// react-native's VirtualizedListCellRenderer only ever passes {highlighted, leadingItem} to +// ItemSeparatorComponent -- there is no trailingItem on native. +test('draws the orange line when the list supplies react-native separator props', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(true) +}) + +test('leaves the older message of the pair alone', () => { + const {container} = render( + + + + ) + expect(drewOrangeLine(container)).toBe(false) +}) diff --git a/shared/chat/conversation/messages/separator.tsx b/shared/chat/conversation/messages/separator.tsx index 56315c22b81a..81366147e1b8 100644 --- a/shared/chat/conversation/messages/separator.tsx +++ b/shared/chat/conversation/messages/separator.tsx @@ -124,4 +124,13 @@ const useStyles = Kb.Styles.createStyleHook( }) as const ) +// react-native hands ItemSeparatorComponent {highlighted, leadingItem} and nothing else -- there is +// no trailingItem on native. The separator belongs to cell i and sits between items i and i+1, and +// the thread list reverses its data, so item i is the newer of the pair: exactly the message +// desktop passes as trailingItem. +export const NativeSeparator = (p: {leadingItem?: T.Chat.Ordinal}) => { + const {leadingItem} = p + return leadingItem === undefined ? null : +} + export default SeparatorConnector diff --git a/shared/tsconfig.native.json b/shared/tsconfig.native.json index 399783d67966..4f6dba111dfd 100644 --- a/shared/tsconfig.native.json +++ b/shared/tsconfig.native.json @@ -18,6 +18,7 @@ "./common-adapters/icon.constants-gen.native.tsx", "./common-adapters/icon.constants-gen.shared.tsx", "./chat/conversation/normal/container.test.tsx", + "./chat/conversation/messages/separator.test.tsx", "./chat/conversation/messages/system-users-added-to-conv/container.test.tsx", "./chat/conversation/messages/text/coinflip/results.test.tsx", "./common-adapters/markdown/index.test.tsx", From 0ee05d471a45fef831074be181e23fc43a7a9682 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Tue, 8 Sep 2026 18:20:55 -0400 Subject: [PATCH 2/3] fix(chat): give the Catch up pill keyboard access ClickableBox is a bare div on desktop, so the pill was unreachable from the keyboard. Add an opt-in asButton prop for button semantics and enter/space activation rather than turning every clickable row into a tab stop. --- .../conversation/list-area/catch-up.test.tsx | 28 ++++++++++++++++--- .../chat/conversation/list-area/catch-up.tsx | 1 + .../conversation/messages/separator.test.tsx | 2 +- shared/common-adapters/box.tsx | 19 ++++++++++++- 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/shared/chat/conversation/list-area/catch-up.test.tsx b/shared/chat/conversation/list-area/catch-up.test.tsx index c314e0ad462f..f0bf5dcd1587 100644 --- a/shared/chat/conversation/list-area/catch-up.test.tsx +++ b/shared/chat/conversation/list-area/catch-up.test.tsx @@ -2,9 +2,9 @@ /// import * as React from 'react' import * as T from '@/constants/types' -import {act, cleanup, render} from '@testing-library/react' +import {act, cleanup, fireEvent, render, screen} from '@testing-library/react' import {OrangeLineContext} from '../orange-line-context' -import {shouldShowCatchUp, useCatchUp} from './catch-up' +import {CatchUp, shouldShowCatchUp, useCatchUp} from './catch-up' const ord = T.Chat.numberToOrdinal @@ -72,9 +72,9 @@ const Probe = (p: {loaded: boolean}) => { } const Tree = (p: {loaded?: boolean; orangeLineOrdinal: T.Chat.Ordinal}) => ( - + - + ) describe('useCatchUp', () => { @@ -116,3 +116,23 @@ describe('useCatchUp', () => { expect(seen?.showCatchUp).toBe(false) }) }) + +// The pill is a ClickableBox, which is a bare div by default: without button semantics it is not a +// tab stop and enter/space do nothing, so keyboard users have no way to reach the unread line. +describe('CatchUp', () => { + afterEach(cleanup) + + test('is reachable from the keyboard', () => { + render() + expect(screen.getByRole('button')).toHaveProperty('tabIndex', 0) + }) + + test('activates on enter and on space', () => { + const onClick = jest.fn() + render() + const pill = screen.getByRole('button') + fireEvent.keyDown(pill, {key: 'Enter'}) + fireEvent.keyDown(pill, {key: ' '}) + expect(onClick).toHaveBeenCalledTimes(2) + }) +}) diff --git a/shared/chat/conversation/list-area/catch-up.tsx b/shared/chat/conversation/list-area/catch-up.tsx index c66a8c28252c..fd2ac7d55ac1 100644 --- a/shared/chat/conversation/list-area/catch-up.tsx +++ b/shared/chat/conversation/list-area/catch-up.tsx @@ -90,6 +90,7 @@ export const CatchUp = (p: {onClick: () => void}) => { return ( ({useCurrentUserState: () => 'testuser' import Separator, {NativeSeparator} from './separator' const Tree = (p: {children: React.ReactNode}) => ( - {p.children} + {p.children} ) // The line is a 1px bar painted with the orange theme token, so its presence is what proves the diff --git a/shared/common-adapters/box.tsx b/shared/common-adapters/box.tsx index 438b4286b0f6..a9019f17a499 100644 --- a/shared/common-adapters/box.tsx +++ b/shared/common-adapters/box.tsx @@ -313,15 +313,28 @@ export type ClickableBoxProps = Box2Props & { onClick?: (e?: React.BaseSyntheticEvent) => void onLongPress?: () => void hitSlop?: number + // Opt-in button semantics: a tab stop on desktop, activated with enter/space, announced as a + // button. Off by default because most clickable boxes wrap rows and whole cards, and making + // every one of them a tab stop would bury the real controls. + asButton?: boolean } export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref}) => { - const {onClick, onLongPress, hitSlop, ref, ...box2p} = p + const {onClick, onLongPress, hitSlop, asButton, ref, ...box2p} = p if (!isMobile) { const {children, style: _style, onMouseOver, onMouseEnter, onMouseDown, onMouseLeave, onMouseMove, onMouseUp, onContextMenu, testID, flex, title, tooltip} = box2p const cn = box2ClassNames(box2p, 'clickable-box2') const s = Styles.collapseStyles([flex != null && flex !== 1 ? {flex} : undefined, _style]) as React.CSSProperties + const onKeyDown = + asButton && onClick + ? (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + onClick(e) + } + } + : undefined return (
} @@ -330,13 +343,16 @@ export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref {children} @@ -354,6 +370,7 @@ export const ClickableBox = (p: ClickableBoxProps & {ref?: React.Ref { onClick(e) } : undefined} pointerEvents={pointerEvents} + role={asButton ? 'button' : undefined} style={s} testID={box2p.testID} > From 9431e6160be1a64cf0e6bce9811951269c5afc02 Mon Sep 17 00:00:00 2001 From: chrisnojima Date: Tue, 8 Sep 2026 18:23:31 -0400 Subject: [PATCH 3/3] refactor: render contexts directly instead of via .Provider React 19 renders a context itself as the provider. The production chat tree already did this; the rest of the app and the tests were still on the legacy form. --- .../chat/conversation/input-area/suggestors/commands.tsx | 8 ++++---- shared/chat/conversation/team-hooks.tsx | 2 +- shared/fs/browser/edit-state.tsx | 4 ++-- shared/fs/browser/sort-state.tsx | 4 ++-- shared/fs/common/daemon.tsx | 6 +++--- shared/fs/common/error-state.tsx | 6 +++--- shared/fs/common/hooks.tsx | 6 +++--- shared/fs/common/sfmi.tsx | 4 ++-- shared/fs/common/status.tsx | 8 ++++---- shared/styles/theme.tsx | 2 +- shared/teams/common/activity.tsx | 2 +- shared/teams/common/selection-state.tsx | 4 ++-- shared/teams/common/use-loaded-team-channels.tsx | 2 +- shared/teams/team/use-loaded-team.tsx | 2 +- shared/teams/use-teams-list.tsx | 6 +++--- shared/util/use-rpc-load.test.tsx | 2 +- 16 files changed, 34 insertions(+), 34 deletions(-) diff --git a/shared/chat/conversation/input-area/suggestors/commands.tsx b/shared/chat/conversation/input-area/suggestors/commands.tsx index 2e6d207c2aa0..5f40d5efb419 100644 --- a/shared/chat/conversation/input-area/suggestors/commands.tsx +++ b/shared/chat/conversation/input-area/suggestors/commands.tsx @@ -237,8 +237,8 @@ export const List = (p: ListProps) => { const {botCommands} = useConversationMeta(conversationIDKey) const items = useDataSource({conversationIDKey, filter, inputSnapshot, suppressCommandSuggestions}) return ( - - + + { loading={false} rowHeight={rowHeight} /> - - + + ) } diff --git a/shared/chat/conversation/team-hooks.tsx b/shared/chat/conversation/team-hooks.tsx index 94d264f5709b..ac47d161ac5c 100644 --- a/shared/chat/conversation/team-hooks.tsx +++ b/shared/chat/conversation/team-hooks.tsx @@ -202,7 +202,7 @@ export const ChatTeamProvider = (props: React.PropsWithChildren) => { sameAsOuter ) const value: ChatTeamContextValue = sameAsOuter ? outer! : {members, team, teamID} - return {children} + return {children} } export const useChatTeam = (teamID: T.Teams.TeamID, teamname?: string): ChatTeam => { diff --git a/shared/fs/browser/edit-state.tsx b/shared/fs/browser/edit-state.tsx index de350d41bc7d..ddde942d5529 100644 --- a/shared/fs/browser/edit-state.tsx +++ b/shared/fs/browser/edit-state.tsx @@ -321,8 +321,8 @@ export const FsBrowserEditProvider = ({children}: {children: React.ReactNode}) = }) return ( - + {children} - + ) } diff --git a/shared/fs/browser/sort-state.tsx b/shared/fs/browser/sort-state.tsx index 84edb03fb13a..549b7d9d2ca7 100644 --- a/shared/fs/browser/sort-state.tsx +++ b/shared/fs/browser/sort-state.tsx @@ -50,8 +50,8 @@ export const FsBrowserSortProvider = ({children}: {children: React.ReactNode}) = } return ( - + {children} - + ) } diff --git a/shared/fs/common/daemon.tsx b/shared/fs/common/daemon.tsx index 0f1d86b2ca27..df427e8e9031 100644 --- a/shared/fs/common/daemon.tsx +++ b/shared/fs/common/daemon.tsx @@ -184,9 +184,9 @@ export const FsDaemonProvider = ({children}: {children: React.ReactNode}) => { : Constants.unknownKbfsDaemonStatus return ( - - {children} - + + {children} + ) } diff --git a/shared/fs/common/error-state.tsx b/shared/fs/common/error-state.tsx index 6d06dd2cd987..bdfc4a920f0d 100644 --- a/shared/fs/common/error-state.tsx +++ b/shared/fs/common/error-state.tsx @@ -132,7 +132,7 @@ export const FsErrorProvider = ({children}: {children: React.ReactNode}) => { }) return ( - { }} > {children} - + ) } @@ -160,7 +160,7 @@ export const FsErrorContextBridge = ({ }: { children: React.ReactNode value: FsErrorContextType | null -}) => {children} +}) => {children} export const useFsErrors = () => { const routeErrors = React.useContext(FsErrorContext) diff --git a/shared/fs/common/hooks.tsx b/shared/fs/common/hooks.tsx index 5b4e61244bf4..51bc1e2d546c 100644 --- a/shared/fs/common/hooks.tsx +++ b/shared/fs/common/hooks.tsx @@ -107,7 +107,7 @@ export const FsDataContextBridge = ({ }: { children: React.ReactNode value: FsDataContextType | null -}) => {children} +}) => {children} type DownloadStartType = 'download' | 'share' | 'saveMedia' @@ -619,7 +619,7 @@ const FsDataProviderForUsername = ({ } return ( - {children} - + ) } diff --git a/shared/fs/common/sfmi.tsx b/shared/fs/common/sfmi.tsx index 063d2ce9c0de..113596f37603 100644 --- a/shared/fs/common/sfmi.tsx +++ b/shared/fs/common/sfmi.tsx @@ -342,7 +342,7 @@ export const SystemFileManagerIntegrationProvider = ({ ) return ( - {children} - + ) } diff --git a/shared/fs/common/status.tsx b/shared/fs/common/status.tsx index be63ea4ea685..8fde61933861 100644 --- a/shared/fs/common/status.tsx +++ b/shared/fs/common/status.tsx @@ -386,11 +386,11 @@ const FsStatusDataProvider = ({children}: {children: React.ReactNode}) => { connected && fsStatusState.generation === currentGeneration ? fsStatusState : emptyFsStatusState return ( - - + + {children} - - + + ) } diff --git a/shared/styles/theme.tsx b/shared/styles/theme.tsx index 0b5ebbef5536..fe5814662b09 100644 --- a/shared/styles/theme.tsx +++ b/shared/styles/theme.tsx @@ -45,7 +45,7 @@ export const ThemeProvider = (p: {children: React.ReactNode}) => { // The selector is constant-false off Android, so the store never notifies there. const isDarkMode = useDarkModeState(s => isAndroid && s.isDarkMode()) return ( - {p.children} + {p.children} ) } diff --git a/shared/teams/common/activity.tsx b/shared/teams/common/activity.tsx index 5473a08eb6f8..527034ba445e 100644 --- a/shared/teams/common/activity.tsx +++ b/shared/teams/common/activity.tsx @@ -104,7 +104,7 @@ export const ActivityLevelsProvider = (props: React.PropsWithChildren) => { ) ) const value = useActivityLevelsRaw(cache) - return {children} + return {children} } const Activity = (p: Props) => { diff --git a/shared/teams/common/selection-state.tsx b/shared/teams/common/selection-state.tsx index 161ce65d14ca..8090c017d31a 100644 --- a/shared/teams/common/selection-state.tsx +++ b/shared/teams/common/selection-state.tsx @@ -94,7 +94,7 @@ export const TeamSelectionProvider = (props: TeamSelectionProviderProps) => { setMemberSelected: members.set, } - return {props.children} + return {props.children} } export const useTeamSelectionState = () => { @@ -114,7 +114,7 @@ export const ChannelSelectionProvider = (props: ChannelSelectionProviderProps) = setMemberSelected: members.set, } - return {props.children} + return {props.children} } export const useChannelSelectionState = () => { diff --git a/shared/teams/common/use-loaded-team-channels.tsx b/shared/teams/common/use-loaded-team-channels.tsx index 8755f7c95226..d9e1d39d149b 100644 --- a/shared/teams/common/use-loaded-team-channels.tsx +++ b/shared/teams/common/use-loaded-team-channels.tsx @@ -254,7 +254,7 @@ export const LoadedTeamChannelsProvider = ( () => ({...loadedTeamChannels, teamID}), [loadedTeamChannels, teamID] ) - return {children} + return {children} } export const useLoadedTeamChannels = ( diff --git a/shared/teams/team/use-loaded-team.tsx b/shared/teams/team/use-loaded-team.tsx index e0774bbd51b8..c15ba232392d 100644 --- a/shared/teams/team/use-loaded-team.tsx +++ b/shared/teams/team/use-loaded-team.tsx @@ -206,7 +206,7 @@ export const LoadedTeamProvider = (props: React.PropsWithChildren<{teamID: T.Tea const {children, teamID} = props const loadedTeam = useLoadedTeamRaw(teamID) const value = React.useMemo(() => ({...loadedTeam, teamID}), [loadedTeam, teamID]) - return {children} + return {children} } export const useLoadedTeam = (teamID: T.Teams.TeamID, enabled = true): LoadedTeam => { diff --git a/shared/teams/use-teams-list.tsx b/shared/teams/use-teams-list.tsx index ee25ff4f86e0..8188cf30aaf0 100644 --- a/shared/teams/use-teams-list.tsx +++ b/shared/teams/use-teams-list.tsx @@ -213,9 +213,9 @@ export const LoadedTeamsListProvider = (props: React.PropsWithChildren) => { const teamsList = useTeamsListRaw() const teamsRoleMap = useTeamsRoleMapRaw() return ( - - {props.children} - + + {props.children} + ) } diff --git a/shared/util/use-rpc-load.test.tsx b/shared/util/use-rpc-load.test.tsx index 53f90dc8837c..db45b3da71af 100644 --- a/shared/util/use-rpc-load.test.tsx +++ b/shared/util/use-rpc-load.test.tsx @@ -33,7 +33,7 @@ const makeNav = () => { }) } const wrapper = ({children}: {children: React.ReactNode}) => ( - {children} + {children} ) return {emit, wrapper} }