Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 24 additions & 1 deletion src/components/PostList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface PostListProps {
loadingMore?: boolean;
/** Whether there are more posts available to load */
hasMore?: boolean;
/** Increment this to reset selection and scroll to top (e.g., on refresh) */
refreshKey?: number;
}

/**
Expand All @@ -58,11 +60,14 @@ export function PostList({
onLoadMore,
loadingMore = false,
hasMore = true,
refreshKey,
}: PostListProps) {
const scrollRef = useRef<ScrollBoxRenderable>(null);
// Save scroll position so we can restore when refocused
const savedScrollTop = useRef(0);
const wasFocused = useRef(focused);
// Track previous refreshKey to detect when refresh is triggered
const prevRefreshKey = useRef(refreshKey);

// Restore scroll position when gaining focus
useEffect(() => {
Expand All @@ -77,7 +82,7 @@ export function PostList({
wasFocused.current = focused;
}, [focused]);

const { selectedIndex } = useListNavigation({
const { selectedIndex, setSelectedIndex } = useListNavigation({
itemCount: posts.length,
enabled: focused,
onSelect: (index) => {
Expand All @@ -93,6 +98,24 @@ export function PostList({
},
});

// Reset to top when refreshKey changes (user explicitly triggered refresh)
useEffect(() => {
// Skip on initial mount (prevRefreshKey.current will equal refreshKey)
if (
prevRefreshKey.current !== undefined &&
refreshKey !== undefined &&
refreshKey !== prevRefreshKey.current
) {
setSelectedIndex(0);
if (scrollRef.current) {
scrollRef.current.scrollTo(0);
}
savedScrollTop.current = 0;
}

prevRefreshKey.current = refreshKey;
}, [refreshKey, setSelectedIndex]);

// Notify parent of selection changes (e.g., for collapsible headers)
useEffect(() => {
onSelectedIndexChange?.(selectedIndex);
Expand Down
7 changes: 6 additions & 1 deletion src/experiments/TimelineScreenExperimental.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/

import { useKeyboard } from "@opentui/react";
import { useEffect, useRef } from "react";
import { useEffect, useRef, useState } from "react";

import type { XClient } from "@/api/client";
import type { TweetData } from "@/api/types";
Expand Down Expand Up @@ -130,6 +130,9 @@ export function TimelineScreenExperimental({
initialTab: preferences.timeline.default_tab,
});

// Track refresh to reset PostList selection/scroll
const [refreshKey, setRefreshKey] = useState(0);

// Report post count to parent
useEffect(() => {
onPostCountChange?.(posts.length);
Expand Down Expand Up @@ -157,6 +160,7 @@ export function TimelineScreenExperimental({
break;
case "r":
refresh();
setRefreshKey((k) => k + 1);
break;
}
});
Expand Down Expand Up @@ -212,6 +216,7 @@ export function TimelineScreenExperimental({
onLoadMore={fetchNextPage}
loadingMore={isFetchingNextPage}
hasMore={hasNextPage}
refreshKey={refreshKey}
/>
</box>
);
Expand Down
7 changes: 6 additions & 1 deletion src/screens/BookmarksScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { useKeyboard } from "@opentui/react";
import { useEffect } from "react";
import { useEffect, useState } from "react";

import type { XClient } from "@/api/client";
import type { BookmarkFolder, TweetData } from "@/api/types";
Expand Down Expand Up @@ -101,6 +101,9 @@ export function BookmarksScreen({
fetchNextPage,
} = useBookmarksQuery({ client, folderId: selectedFolder?.id });

// Track refresh to reset PostList selection/scroll
const [refreshKey, setRefreshKey] = useState(0);

// Report post count to parent
useEffect(() => {
onPostCountChange?.(posts.length);
Expand All @@ -117,6 +120,7 @@ export function BookmarksScreen({

if (key.name === "r") {
refresh();
setRefreshKey((k) => k + 1);
}

// Open folder picker with 'f'
Expand Down Expand Up @@ -192,6 +196,7 @@ export function BookmarksScreen({
onLoadMore={fetchNextPage}
loadingMore={isFetchingNextPage}
hasMore={hasNextPage}
refreshKey={refreshKey}
/>
</box>
);
Expand Down
5 changes: 5 additions & 0 deletions src/screens/ProfileScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ export function ProfileScreen({

const [activeTab, setActiveTab] = useState<ProfileTab>("tweets");

// Track refresh to reset PostList selection/scroll
const [refreshKey, setRefreshKey] = useState(0);

// Get current tab index for arrow navigation
const activeTabIndex = useMemo(
() => availableTabs.findIndex((t) => t.key === activeTab),
Expand Down Expand Up @@ -289,6 +292,7 @@ export function ProfileScreen({
break;
case "r":
refresh();
setRefreshKey((k) => k + 1);
break;
case "a":
// Open avatar/profile photo in Quick Look
Expand Down Expand Up @@ -769,6 +773,7 @@ export function ProfileScreen({
onBookmark={onBookmark}
getActionState={getActionState}
initActionState={initActionState}
refreshKey={refreshKey}
/>
) : (
<box style={{ padding: 1, flexGrow: 1 }}>
Expand Down
7 changes: 6 additions & 1 deletion src/screens/TimelineScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { useKeyboard } from "@opentui/react";
import { useEffect } from "react";
import { useEffect, useState } from "react";

import type { XClient } from "@/api/client";
import type { TweetData } from "@/api/types";
Expand Down Expand Up @@ -87,6 +87,9 @@ export function TimelineScreen({
client,
});

// Track refresh to reset PostList selection/scroll
const [refreshKey, setRefreshKey] = useState(0);

// Report post count to parent
useEffect(() => {
onPostCountChange?.(posts.length);
Expand All @@ -105,6 +108,7 @@ export function TimelineScreen({
break;
case "r":
refresh();
setRefreshKey((k) => k + 1);
break;
}
});
Expand Down Expand Up @@ -156,6 +160,7 @@ export function TimelineScreen({
onLoadMore={fetchNextPage}
loadingMore={isFetchingNextPage}
hasMore={hasNextPage}
refreshKey={refreshKey}
/>
</box>
);
Expand Down
Loading