xfeed 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +348 -194
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -439,7 +439,7 @@ import { cac } from "cac";
439
439
 
440
440
  // src/app.tsx
441
441
  import { useKeyboard as useKeyboard15, useRenderer as useRenderer2 } from "@opentui/react";
442
- import { useCallback as useCallback16, useEffect as useEffect21, useRef as useRef12, useState as useState23 } from "react";
442
+ import { useCallback as useCallback15, useEffect as useEffect20, useRef as useRef12, useState as useState23 } from "react";
443
443
 
444
444
  // src/components/Footer.tsx
445
445
  import { useTerminalDimensions } from "@opentui/react";
@@ -456,7 +456,10 @@ var colors = {
456
456
  mention: "#9B59B6",
457
457
  quoted: "#3498DB",
458
458
  reply: "#95A5A6",
459
- handle: "#6B8A9E"
459
+ handle: "#6B8A9E",
460
+ liked: "#E0245E",
461
+ bookmarked: "#1DA1F2",
462
+ actionInactive: "#888888"
460
463
  };
461
464
 
462
465
  // src/components/Footer.tsx
@@ -466,8 +469,9 @@ var DEFAULT_BINDINGS = [
466
469
  { key: "l", label: "like" },
467
470
  { key: "b", label: "bookmark" },
468
471
  { key: "r", label: "refresh" },
469
- { key: "n", label: "notifs" },
470
- { key: "Tab", label: "view" },
472
+ { key: "1", label: "home" },
473
+ { key: "2", label: "bookmarks" },
474
+ { key: "3", label: "notifs" },
471
475
  { key: "q", label: "quit" }
472
476
  ];
473
477
  function KeybindingItem({ binding }) {
@@ -1916,7 +1920,7 @@ function useModal() {
1916
1920
 
1917
1921
  // src/experiments/index.tsx
1918
1922
  import { QueryClientProvider } from "@tanstack/react-query";
1919
- import { useState as useState15 } from "react";
1923
+ import { useState as useState16 } from "react";
1920
1924
 
1921
1925
  // src/experiments/query-client.ts
1922
1926
  import { QueryClient } from "@tanstack/react-query";
@@ -1998,7 +2002,7 @@ var queryKeys = {
1998
2002
 
1999
2003
  // src/experiments/TimelineScreenExperimental.tsx
2000
2004
  import { useKeyboard as useKeyboard9 } from "@opentui/react";
2001
- import { useEffect as useEffect9, useRef as useRef4 } from "react";
2005
+ import { useEffect as useEffect9, useRef as useRef5 } from "react";
2002
2006
 
2003
2007
  // src/components/ErrorBanner.tsx
2004
2008
  import { useEffect as useEffect6 } from "react";
@@ -2230,7 +2234,10 @@ function ErrorBanner({
2230
2234
 
2231
2235
  // src/components/PostList.tsx
2232
2236
  import { useKeyboard as useKeyboard8 } from "@opentui/react";
2233
- import { useEffect as useEffect7, useRef as useRef2 } from "react";
2237
+ import { useEffect as useEffect7, useRef as useRef3 } from "react";
2238
+
2239
+ // src/components/PostCard.tsx
2240
+ import { useRef as useRef2, useState as useState10 } from "react";
2234
2241
 
2235
2242
  // src/components/QuotedPostCard.tsx
2236
2243
  import { jsxDEV as jsxDEV11 } from "@opentui/react/jsx-dev-runtime";
@@ -2390,8 +2397,85 @@ function PostCard({
2390
2397
  isJustLiked,
2391
2398
  isJustBookmarked,
2392
2399
  parentAuthorUsername,
2393
- mainPostAuthorUsername
2400
+ mainPostAuthorUsername,
2401
+ onCardClick,
2402
+ onLikeClick,
2403
+ onBookmarkClick
2394
2404
  }) {
2405
+ const cardDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
2406
+ const likeDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
2407
+ const bookmarkDragState = useRef2({ isDragging: false, startX: 0, startY: 0 });
2408
+ const DRAG_THRESHOLD = 3;
2409
+ const handleCardMouse = onCardClick ? (event) => {
2410
+ if (event.button !== 0)
2411
+ return;
2412
+ if (event.type === "down") {
2413
+ cardDragState.current = {
2414
+ isDragging: false,
2415
+ startX: event.x,
2416
+ startY: event.y
2417
+ };
2418
+ } else if (event.type === "drag") {
2419
+ const dx = Math.abs(event.x - cardDragState.current.startX);
2420
+ const dy = Math.abs(event.y - cardDragState.current.startY);
2421
+ if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
2422
+ cardDragState.current.isDragging = true;
2423
+ }
2424
+ } else if (event.type === "up") {
2425
+ if (!cardDragState.current.isDragging) {
2426
+ onCardClick();
2427
+ }
2428
+ cardDragState.current.isDragging = false;
2429
+ }
2430
+ } : undefined;
2431
+ const handleLikeMouse = onLikeClick ? (event) => {
2432
+ if (event.button !== 0)
2433
+ return;
2434
+ event.stopPropagation();
2435
+ if (event.type === "down") {
2436
+ likeDragState.current = {
2437
+ isDragging: false,
2438
+ startX: event.x,
2439
+ startY: event.y
2440
+ };
2441
+ } else if (event.type === "drag") {
2442
+ const dx = Math.abs(event.x - likeDragState.current.startX);
2443
+ const dy = Math.abs(event.y - likeDragState.current.startY);
2444
+ if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
2445
+ likeDragState.current.isDragging = true;
2446
+ }
2447
+ } else if (event.type === "up") {
2448
+ if (!likeDragState.current.isDragging) {
2449
+ onLikeClick();
2450
+ }
2451
+ likeDragState.current.isDragging = false;
2452
+ }
2453
+ } : undefined;
2454
+ const handleBookmarkMouse = onBookmarkClick ? (event) => {
2455
+ if (event.button !== 0)
2456
+ return;
2457
+ event.stopPropagation();
2458
+ if (event.type === "down") {
2459
+ bookmarkDragState.current = {
2460
+ isDragging: false,
2461
+ startX: event.x,
2462
+ startY: event.y
2463
+ };
2464
+ } else if (event.type === "drag") {
2465
+ const dx = Math.abs(event.x - bookmarkDragState.current.startX);
2466
+ const dy = Math.abs(event.y - bookmarkDragState.current.startY);
2467
+ if (dx > DRAG_THRESHOLD || dy > DRAG_THRESHOLD) {
2468
+ bookmarkDragState.current.isDragging = true;
2469
+ }
2470
+ } else if (event.type === "up") {
2471
+ if (!bookmarkDragState.current.isDragging) {
2472
+ onBookmarkClick();
2473
+ }
2474
+ bookmarkDragState.current.isDragging = false;
2475
+ }
2476
+ } : undefined;
2477
+ const [isLikeHovered, setIsLikeHovered] = useState10(false);
2478
+ const [isBookmarkHovered, setIsBookmarkHovered] = useState10(false);
2395
2479
  const textToDisplay = parentAuthorUsername ? stripLeadingMention(post.text, parentAuthorUsername) : post.text;
2396
2480
  const displayText = truncateText(textToDisplay, MAX_TEXT_LINES3);
2397
2481
  const showShowMore = isTruncated(textToDisplay, MAX_TEXT_LINES3);
@@ -2399,6 +2483,7 @@ function PostCard({
2399
2483
  const hasMedia = post.media && post.media.length > 0;
2400
2484
  return /* @__PURE__ */ jsxDEV13("box", {
2401
2485
  id,
2486
+ onMouse: handleCardMouse,
2402
2487
  style: {
2403
2488
  flexDirection: "column",
2404
2489
  marginBottom: 1,
@@ -2472,7 +2557,10 @@ function PostCard({
2472
2557
  ]
2473
2558
  }, undefined, true, undefined, this),
2474
2559
  /* @__PURE__ */ jsxDEV13("text", {
2475
- fg: isJustLiked ? colors.success : isLiked ? colors.error : colors.muted,
2560
+ onMouse: handleLikeMouse,
2561
+ onMouseOver: () => setIsLikeHovered(true),
2562
+ onMouseOut: () => setIsLikeHovered(false),
2563
+ fg: isJustLiked ? colors.success : isLikeHovered ? colors.liked : isLiked ? colors.liked : colors.actionInactive,
2476
2564
  children: [
2477
2565
  " ",
2478
2566
  isLiked ? HEART_FILLED : /* @__PURE__ */ jsxDEV13("b", {
@@ -2481,7 +2569,10 @@ function PostCard({
2481
2569
  ]
2482
2570
  }, undefined, true, undefined, this),
2483
2571
  /* @__PURE__ */ jsxDEV13("text", {
2484
- fg: isJustBookmarked ? colors.success : isBookmarked ? colors.primary : colors.muted,
2572
+ onMouse: handleBookmarkMouse,
2573
+ onMouseOver: () => setIsBookmarkHovered(true),
2574
+ onMouseOut: () => setIsBookmarkHovered(false),
2575
+ fg: isJustBookmarked ? colors.success : isBookmarkHovered ? colors.bookmarked : isBookmarked ? colors.bookmarked : colors.actionInactive,
2485
2576
  children: [
2486
2577
  " ",
2487
2578
  isBookmarked ? FLAG_FILLED : /* @__PURE__ */ jsxDEV13("b", {
@@ -2540,9 +2631,9 @@ function PostList({
2540
2631
  loadingMore = false,
2541
2632
  hasMore = true
2542
2633
  }) {
2543
- const scrollRef = useRef2(null);
2544
- const savedScrollTop = useRef2(0);
2545
- const wasFocused = useRef2(focused);
2634
+ const scrollRef = useRef3(null);
2635
+ const savedScrollTop = useRef3(0);
2636
+ const wasFocused = useRef3(focused);
2546
2637
  useEffect7(() => {
2547
2638
  const scrollbox = scrollRef.current;
2548
2639
  if (!scrollbox)
@@ -2650,7 +2741,10 @@ function PostList({
2650
2741
  isLiked: actionState?.liked,
2651
2742
  isBookmarked: actionState?.bookmarked,
2652
2743
  isJustLiked: actionState?.justLiked,
2653
- isJustBookmarked: actionState?.justBookmarked
2744
+ isJustBookmarked: actionState?.justBookmarked,
2745
+ onCardClick: () => onPostSelect?.(post),
2746
+ onLikeClick: () => onLike?.(post),
2747
+ onBookmarkClick: () => onBookmark?.(post)
2654
2748
  }, post.id, false, undefined, this);
2655
2749
  }),
2656
2750
  loadingMore ? /* @__PURE__ */ jsxDEV14("box", {
@@ -2697,7 +2791,7 @@ function usePreferences() {
2697
2791
 
2698
2792
  // src/experiments/use-timeline-query.ts
2699
2793
  import { useInfiniteQuery } from "@tanstack/react-query";
2700
- import { useCallback as useCallback5, useEffect as useEffect8, useMemo, useRef as useRef3, useState as useState10 } from "react";
2794
+ import { useCallback as useCallback5, useEffect as useEffect8, useMemo, useRef as useRef4, useState as useState11 } from "react";
2701
2795
  async function fetchTimelinePage(client, tab, cursor) {
2702
2796
  const result = cursor ? tab === "for_you" ? await client.getHomeTimeline(30, cursor) : await client.getHomeLatestTimeline(30, cursor) : tab === "for_you" ? await client.getHomeTimelineV2(30) : await client.getHomeLatestTimelineV2(30);
2703
2797
  if (!result.success) {
@@ -2728,9 +2822,9 @@ function useTimelineQuery({
2728
2822
  initialTab = "for_you",
2729
2823
  refreshBannerDelay = DEFAULT_REFRESH_BANNER_DELAY
2730
2824
  }) {
2731
- const [tab, setTabState] = useState10(initialTab);
2732
- const [showRefreshBanner, setShowRefreshBanner] = useState10(false);
2733
- const bannerTimerRef = useRef3(null);
2825
+ const [tab, setTabState] = useState11(initialTab);
2826
+ const [showRefreshBanner, setShowRefreshBanner] = useState11(false);
2827
+ const bannerTimerRef = useRef4(null);
2734
2828
  const {
2735
2829
  data,
2736
2830
  isLoading,
@@ -2896,7 +2990,7 @@ function TimelineScreenExperimental({
2896
2990
  useEffect9(() => {
2897
2991
  onPostCountChange?.(posts.length);
2898
2992
  }, [posts.length, onPostCountChange]);
2899
- const hasCalledInitialLoadComplete = useRef4(false);
2993
+ const hasCalledInitialLoadComplete = useRef5(false);
2900
2994
  useEffect9(() => {
2901
2995
  if (!isLoading && !hasCalledInitialLoadComplete.current) {
2902
2996
  hasCalledInitialLoadComplete.current = true;
@@ -2913,6 +3007,9 @@ function TimelineScreenExperimental({
2913
3007
  case "2":
2914
3008
  setTab("following");
2915
3009
  break;
3010
+ case "tab":
3011
+ setTab(tab === "for_you" ? "following" : "for_you");
3012
+ break;
2916
3013
  case "r":
2917
3014
  refresh();
2918
3015
  break;
@@ -2995,7 +3092,105 @@ function TimelineScreenExperimental({
2995
3092
  }
2996
3093
  // src/experiments/use-profile-query.ts
2997
3094
  import { useQuery } from "@tanstack/react-query";
2998
- import { useCallback as useCallback6, useState as useState11 } from "react";
3095
+ import { useCallback as useCallback6, useState as useState12 } from "react";
3096
+ function useProfileQuery({
3097
+ client,
3098
+ username,
3099
+ isSelf = false
3100
+ }) {
3101
+ const [likesEnabled, setLikesEnabled] = useState12(false);
3102
+ const {
3103
+ data: profileData,
3104
+ isLoading: isProfileLoading,
3105
+ error: profileError,
3106
+ refetch: refetchProfile,
3107
+ isRefetching: isProfileRefetching
3108
+ } = useQuery({
3109
+ queryKey: queryKeys.user.profile(username.toLowerCase()),
3110
+ queryFn: async () => {
3111
+ const result = await client.getUserByScreenName(username);
3112
+ if (!result.success) {
3113
+ throw new Error(result.error ?? "Failed to load profile");
3114
+ }
3115
+ if (!result.user) {
3116
+ throw new Error("User not found");
3117
+ }
3118
+ return result.user;
3119
+ }
3120
+ });
3121
+ const {
3122
+ data: tweetsData,
3123
+ isLoading: isTweetsLoading,
3124
+ refetch: refetchTweets,
3125
+ isRefetching: isTweetsRefetching
3126
+ } = useQuery({
3127
+ queryKey: queryKeys.user.tweets(profileData?.id ?? ""),
3128
+ queryFn: async () => {
3129
+ if (!profileData?.id)
3130
+ return [];
3131
+ const result = await client.getUserTweets(profileData.id, 20);
3132
+ if (!result.success) {
3133
+ return [];
3134
+ }
3135
+ return result.tweets ?? [];
3136
+ },
3137
+ enabled: !!profileData?.id
3138
+ });
3139
+ const {
3140
+ data: likesData,
3141
+ isLoading: isLikesLoading,
3142
+ error: likesError,
3143
+ refetch: refetchLikes,
3144
+ isFetched: likesFetched
3145
+ } = useQuery({
3146
+ queryKey: queryKeys.user.likes(),
3147
+ queryFn: async () => {
3148
+ const result = await client.getLikes(20);
3149
+ if (!result.success) {
3150
+ throw new Error(result.error ?? "Failed to load likes");
3151
+ }
3152
+ return result.tweets ?? [];
3153
+ },
3154
+ enabled: isSelf && likesEnabled
3155
+ });
3156
+ const fetchLikes = useCallback6(() => {
3157
+ if (isSelf && !likesEnabled) {
3158
+ setLikesEnabled(true);
3159
+ } else if (isSelf && likesEnabled) {
3160
+ refetchLikes();
3161
+ }
3162
+ }, [isSelf, likesEnabled, refetchLikes]);
3163
+ const refresh = useCallback6(() => {
3164
+ refetchProfile();
3165
+ if (profileData?.id) {
3166
+ refetchTweets();
3167
+ }
3168
+ if (isSelf && likesEnabled) {
3169
+ refetchLikes();
3170
+ }
3171
+ }, [
3172
+ refetchProfile,
3173
+ refetchTweets,
3174
+ refetchLikes,
3175
+ profileData?.id,
3176
+ isSelf,
3177
+ likesEnabled
3178
+ ]);
3179
+ return {
3180
+ user: profileData ?? null,
3181
+ tweets: tweetsData ?? [],
3182
+ isLoading: isProfileLoading,
3183
+ isTweetsLoading,
3184
+ error: profileError instanceof Error ? profileError.message : null,
3185
+ refresh,
3186
+ likedTweets: likesData ?? [],
3187
+ isLikesLoading,
3188
+ likesError: likesError instanceof Error ? likesError.message : null,
3189
+ fetchLikes,
3190
+ likesFetched,
3191
+ isRefetching: isProfileRefetching || isTweetsRefetching
3192
+ };
3193
+ }
2999
3194
  // src/experiments/use-post-detail-query.ts
3000
3195
  import {
3001
3196
  useInfiniteQuery as useInfiniteQuery2,
@@ -3183,7 +3378,7 @@ function useBookmarksQuery({
3183
3378
  }
3184
3379
  // src/experiments/use-bookmark-mutation.ts
3185
3380
  import { useMutation, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
3186
- import { useCallback as useCallback8, useRef as useRef5, useState as useState12 } from "react";
3381
+ import { useCallback as useCallback8, useRef as useRef6, useState as useState13 } from "react";
3187
3382
  var JUST_ACTED_DURATION = 600;
3188
3383
  function useBookmarkMutation({
3189
3384
  client,
@@ -3191,9 +3386,9 @@ function useBookmarkMutation({
3191
3386
  onError
3192
3387
  }) {
3193
3388
  const queryClient = useQueryClient3();
3194
- const [pendingTweets, setPendingTweets] = useState12(new Set);
3195
- const [justBookmarkedTweets, setJustBookmarkedTweets] = useState12(new Set);
3196
- const justBookmarkedTimeouts = useRef5(new Map);
3389
+ const [pendingTweets, setPendingTweets] = useState13(new Set);
3390
+ const [justBookmarkedTweets, setJustBookmarkedTweets] = useState13(new Set);
3391
+ const justBookmarkedTimeouts = useRef6(new Map);
3197
3392
  const addMutation = useMutation({
3198
3393
  mutationFn: async (tweet) => {
3199
3394
  const result = await client.createBookmark(tweet.id);
@@ -3388,7 +3583,7 @@ import { useEffect as useEffect12, useMemo as useMemo5 } from "react";
3388
3583
 
3389
3584
  // src/components/ThreadView.prototype.tsx
3390
3585
  import { useKeyboard as useKeyboard10 } from "@opentui/react";
3391
- import { useState as useState13, useRef as useRef6, useEffect as useEffect11, useMemo as useMemo4 } from "react";
3586
+ import { useState as useState14, useRef as useRef7, useEffect as useEffect11, useMemo as useMemo4 } from "react";
3392
3587
  import { jsxDEV as jsxDEV17 } from "@opentui/react/jsx-dev-runtime";
3393
3588
  var SELECTED_BG = "#2a2a3e";
3394
3589
  var TREE = {
@@ -3627,11 +3822,11 @@ function ThreadViewPrototype({
3627
3822
  onSelectTweet,
3628
3823
  showFooter = true
3629
3824
  }) {
3630
- const scrollRef = useRef6(null);
3631
- const savedScrollTop = useRef6(0);
3632
- const wasFocused = useRef6(focused);
3633
- const [selectedIndex, setSelectedIndex] = useState13(0);
3634
- const [treeState, setTreeState] = useState13(replyTree);
3825
+ const scrollRef = useRef7(null);
3826
+ const savedScrollTop = useRef7(0);
3827
+ const wasFocused = useRef7(focused);
3828
+ const [selectedIndex, setSelectedIndex] = useState14(0);
3829
+ const [treeState, setTreeState] = useState14(replyTree);
3635
3830
  useEffect11(() => {
3636
3831
  const scrollbox = scrollRef.current;
3637
3832
  if (!scrollbox)
@@ -3956,7 +4151,7 @@ function useThreadQuery({
3956
4151
  }
3957
4152
  // src/experiments/use-user-actions.ts
3958
4153
  import { useMutation as useMutation2, useQueryClient as useQueryClient5 } from "@tanstack/react-query";
3959
- import { useCallback as useCallback9, useState as useState14 } from "react";
4154
+ import { useCallback as useCallback9, useState as useState15 } from "react";
3960
4155
  function useUserActions({
3961
4156
  client,
3962
4157
  username,
@@ -3964,7 +4159,7 @@ function useUserActions({
3964
4159
  onError
3965
4160
  }) {
3966
4161
  const queryClient = useQueryClient5();
3967
- const [pendingAction, setPendingAction] = useState14(null);
4162
+ const [pendingAction, setPendingAction] = useState15(null);
3968
4163
  const updateProfileCache = useCallback9((updates) => {
3969
4164
  queryClient.setQueryData(queryKeys.user.profile(username), (old) => {
3970
4165
  if (!old?.success || !old.user)
@@ -4172,7 +4367,7 @@ function getQueryClient() {
4172
4367
  return queryClientInstance;
4173
4368
  }
4174
4369
  function QueryProvider({ children }) {
4175
- const [queryClient] = useState15(() => getQueryClient());
4370
+ const [queryClient] = useState16(() => getQueryClient());
4176
4371
  return /* @__PURE__ */ jsxDEV18(QueryClientProvider, {
4177
4372
  client: queryClient,
4178
4373
  children
@@ -4180,7 +4375,7 @@ function QueryProvider({ children }) {
4180
4375
  }
4181
4376
 
4182
4377
  // src/hooks/useActions.ts
4183
- import { useState as useState16, useCallback as useCallback10, useRef as useRef7 } from "react";
4378
+ import { useState as useState17, useCallback as useCallback10, useRef as useRef8 } from "react";
4184
4379
  var DEFAULT_STATE = {
4185
4380
  liked: false,
4186
4381
  bookmarked: false,
@@ -4198,9 +4393,9 @@ function useActions({
4198
4393
  bookmarkMutation,
4199
4394
  currentFolderId
4200
4395
  }) {
4201
- const [states, setStates] = useState16(new Map);
4202
- const justLikedTimeouts = useRef7(new Map);
4203
- const justBookmarkedTimeouts = useRef7(new Map);
4396
+ const [states, setStates] = useState17(new Map);
4397
+ const justLikedTimeouts = useRef8(new Map);
4398
+ const justBookmarkedTimeouts = useRef8(new Map);
4204
4399
  const getState = useCallback10((tweetId) => {
4205
4400
  const baseState = states.get(tweetId) ?? DEFAULT_STATE;
4206
4401
  if (bookmarkMutation) {
@@ -4377,10 +4572,10 @@ function useActions({
4377
4572
  }
4378
4573
 
4379
4574
  // src/hooks/useNavigation.ts
4380
- import { useState as useState17, useCallback as useCallback11 } from "react";
4575
+ import { useState as useState18, useCallback as useCallback11 } from "react";
4381
4576
  function useNavigation(options) {
4382
4577
  const { initialView, mainViews } = options;
4383
- const [history, setHistory] = useState17([initialView]);
4578
+ const [history, setHistory] = useState18([initialView]);
4384
4579
  const currentView = history[history.length - 1];
4385
4580
  const previousView = history.length > 1 ? history[history.length - 2] : null;
4386
4581
  const canGoBack = history.length > 1;
@@ -4483,7 +4678,7 @@ function ScreenHeader({ folderName, inFolder }) {
4483
4678
  fg: colors.dim,
4484
4679
  children: [
4485
4680
  " ",
4486
- "(f folders, c new",
4681
+ "(Tab/f folders, c new",
4487
4682
  inFolder ? ", e rename, D delete" : "",
4488
4683
  ")"
4489
4684
  ]
@@ -4528,7 +4723,7 @@ function BookmarksScreen({
4528
4723
  if (key.name === "r") {
4529
4724
  refresh();
4530
4725
  }
4531
- if (key.name === "f") {
4726
+ if (key.name === "f" || key.name === "tab") {
4532
4727
  onFolderPickerOpen?.();
4533
4728
  }
4534
4729
  if (key.name === "c") {
@@ -4623,7 +4818,7 @@ import { useKeyboard as useKeyboard12 } from "@opentui/react";
4623
4818
  import { useEffect as useEffect16 } from "react";
4624
4819
 
4625
4820
  // src/components/NotificationList.tsx
4626
- import { useEffect as useEffect14, useRef as useRef8 } from "react";
4821
+ import { useEffect as useEffect14, useRef as useRef9 } from "react";
4627
4822
 
4628
4823
  // src/components/NotificationItem.tsx
4629
4824
  import { jsxDEV as jsxDEV20 } from "@opentui/react/jsx-dev-runtime";
@@ -4721,9 +4916,9 @@ function NotificationList({
4721
4916
  loadingMore = false,
4722
4917
  hasMore = true
4723
4918
  }) {
4724
- const scrollRef = useRef8(null);
4725
- const savedScrollTop = useRef8(0);
4726
- const wasFocused = useRef8(focused);
4919
+ const scrollRef = useRef9(null);
4920
+ const savedScrollTop = useRef9(0);
4921
+ const wasFocused = useRef9(focused);
4727
4922
  useEffect14(() => {
4728
4923
  const scrollbox = scrollRef.current;
4729
4924
  if (!scrollbox)
@@ -4828,7 +5023,7 @@ import { useInfiniteQuery as useInfiniteQuery4 } from "@tanstack/react-query";
4828
5023
  import { appendFileSync } from "fs";
4829
5024
  import { homedir as homedir2 } from "os";
4830
5025
  import { join } from "path";
4831
- import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo6, useRef as useRef9, useState as useState18 } from "react";
5026
+ import { useCallback as useCallback12, useEffect as useEffect15, useMemo as useMemo6, useRef as useRef10, useState as useState19 } from "react";
4832
5027
  var LOG_FILE = join(homedir2(), ".xfeed-notifications.log");
4833
5028
  function log(message, data) {
4834
5029
  const timestamp = new Date().toISOString();
@@ -4911,8 +5106,8 @@ function useNotificationsQuery({
4911
5106
  client,
4912
5107
  pollingInterval = DEFAULT_POLLING_INTERVAL
4913
5108
  }) {
4914
- const lastSeenNewestIdRef = useRef9(null);
4915
- const [newNotificationsCount, setNewNotificationsCount] = useState18(0);
5109
+ const lastSeenNewestIdRef = useRef10(null);
5110
+ const [newNotificationsCount, setNewNotificationsCount] = useState19(0);
4916
5111
  log("useNotificationsQuery render", {
4917
5112
  lastSeenNewestId: lastSeenNewestIdRef.current,
4918
5113
  newNotificationsCount
@@ -4962,7 +5157,7 @@ function useNotificationsQuery({
4962
5157
  lastSeenNewestIdRef.current = newestId ?? null;
4963
5158
  }
4964
5159
  }, [notifications]);
4965
- const lastUnreadSortIndexRef = useRef9(null);
5160
+ const lastUnreadSortIndexRef = useRef10(null);
4966
5161
  useEffect15(() => {
4967
5162
  if (lastUnreadSortIndexRef.current === null && data?.pages?.[0]) {
4968
5163
  const sortIndex = data.pages[0].unreadSortIndex;
@@ -5228,7 +5423,7 @@ function NotificationsScreen({
5228
5423
 
5229
5424
  // src/screens/PostDetailScreen.tsx
5230
5425
  import { useKeyboard as useKeyboard13 } from "@opentui/react";
5231
- import { useState as useState19, useRef as useRef10, useEffect as useEffect17, useCallback as useCallback13 } from "react";
5426
+ import { useState as useState20, useRef as useRef11, useEffect as useEffect17, useCallback as useCallback13 } from "react";
5232
5427
  init_media();
5233
5428
 
5234
5429
  // src/lib/text.tsx
@@ -5356,16 +5551,16 @@ function PostDetailScreen({
5356
5551
  hasMoreReplies,
5357
5552
  loadMoreReplies
5358
5553
  } = usePostDetailQuery({ client, tweet });
5359
- const [isExpanded, setIsExpanded] = useState19(false);
5360
- const [statusMessage, setStatusMessage] = useState19(null);
5361
- const [linkIndex, setLinkIndex] = useState19(0);
5362
- const [mentionIndex, setMentionIndex] = useState19(0);
5363
- const [linkMetadata, setLinkMetadata] = useState19(null);
5364
- const [isLoadingMetadata, setIsLoadingMetadata] = useState19(false);
5365
- const [repliesMode, setRepliesMode] = useState19(false);
5366
- const [mentionsMode, setMentionsMode] = useState19(false);
5367
- const [linkMode, setLinkMode] = useState19(false);
5368
- const scrollRef = useRef10(null);
5554
+ const [isExpanded, setIsExpanded] = useState20(false);
5555
+ const [statusMessage, setStatusMessage] = useState20(null);
5556
+ const [linkIndex, setLinkIndex] = useState20(0);
5557
+ const [mentionIndex, setMentionIndex] = useState20(0);
5558
+ const [linkMetadata, setLinkMetadata] = useState20(null);
5559
+ const [isLoadingMetadata, setIsLoadingMetadata] = useState20(false);
5560
+ const [repliesMode, setRepliesMode] = useState20(false);
5561
+ const [mentionsMode, setMentionsMode] = useState20(false);
5562
+ const [linkMode, setLinkMode] = useState20(false);
5563
+ const scrollRef = useRef11(null);
5369
5564
  useEffect17(() => {
5370
5565
  setRepliesMode(false);
5371
5566
  setMentionsMode(false);
@@ -5622,7 +5817,10 @@ function PostDetailScreen({
5622
5817
  setMentionsMode(true);
5623
5818
  setMentionIndex(0);
5624
5819
  }
5625
- } else if (isBookmarked) {
5820
+ }
5821
+ break;
5822
+ case "f":
5823
+ if (isBookmarked) {
5626
5824
  onMoveToFolder?.();
5627
5825
  }
5628
5826
  break;
@@ -5961,6 +6159,11 @@ function PostDetailScreen({
5961
6159
  }, undefined, true, undefined, this)
5962
6160
  }, undefined, false, undefined, this)
5963
6161
  }, undefined, false, undefined, this) : null;
6162
+ const handleMentionRowClick = useCallback13((username) => (event) => {
6163
+ if (event.type === "up" && event.button === 0) {
6164
+ onProfileOpen?.(username);
6165
+ }
6166
+ }, [onProfileOpen]);
5964
6167
  const firstMention = tweet.mentions?.[0];
5965
6168
  const mentionsContent = hasMentions ? /* @__PURE__ */ jsxDEV24("box", {
5966
6169
  style: { marginTop: 1, paddingLeft: 1, paddingRight: 1 },
@@ -5968,6 +6171,7 @@ function PostDetailScreen({
5968
6171
  style: { flexDirection: "column" },
5969
6172
  children: mentionCount === 1 ? /* @__PURE__ */ jsxDEV24("box", {
5970
6173
  style: { flexDirection: "row" },
6174
+ onMouse: firstMention ? handleMentionRowClick(firstMention.username) : undefined,
5971
6175
  children: [
5972
6176
  /* @__PURE__ */ jsxDEV24("text", {
5973
6177
  fg: colors.dim,
@@ -5997,7 +6201,7 @@ function PostDetailScreen({
5997
6201
  }, undefined, false, undefined, this),
5998
6202
  /* @__PURE__ */ jsxDEV24("text", {
5999
6203
  fg: colors.dim,
6000
- children: " profile)"
6204
+ children: "/click)"
6001
6205
  }, undefined, false, undefined, this)
6002
6206
  ]
6003
6207
  }, undefined, true, undefined, this) : mentionsMode ? /* @__PURE__ */ jsxDEV24(Fragment, {
@@ -6027,7 +6231,7 @@ function PostDetailScreen({
6027
6231
  }, undefined, false, undefined, this),
6028
6232
  /* @__PURE__ */ jsxDEV24("text", {
6029
6233
  fg: colors.dim,
6030
- children: " profile"
6234
+ children: "/click profile"
6031
6235
  }, undefined, false, undefined, this)
6032
6236
  ]
6033
6237
  }, undefined, true, undefined, this),
@@ -6035,6 +6239,7 @@ function PostDetailScreen({
6035
6239
  const isSelected = idx === mentionIndex;
6036
6240
  return /* @__PURE__ */ jsxDEV24("box", {
6037
6241
  style: { flexDirection: "row", marginTop: idx === 0 ? 1 : 0 },
6242
+ onMouse: handleMentionRowClick(mention.username),
6038
6243
  children: [
6039
6244
  /* @__PURE__ */ jsxDEV24("text", {
6040
6245
  fg: isSelected ? colors.mention : colors.muted,
@@ -6057,6 +6262,12 @@ function PostDetailScreen({
6057
6262
  ]
6058
6263
  }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV24("box", {
6059
6264
  style: { flexDirection: "row" },
6265
+ onMouse: (event) => {
6266
+ if (event.type === "up" && event.button === 0) {
6267
+ setMentionsMode(true);
6268
+ setMentionIndex(0);
6269
+ }
6270
+ },
6060
6271
  children: [
6061
6272
  /* @__PURE__ */ jsxDEV24("text", {
6062
6273
  fg: colors.dim,
@@ -6083,7 +6294,7 @@ function PostDetailScreen({
6083
6294
  }, undefined, false, undefined, this),
6084
6295
  /* @__PURE__ */ jsxDEV24("text", {
6085
6296
  fg: colors.dim,
6086
- children: " to navigate)"
6297
+ children: "/click)"
6087
6298
  }, undefined, false, undefined, this)
6088
6299
  ]
6089
6300
  }, undefined, true, undefined, this)
@@ -6187,13 +6398,13 @@ function PostDetailScreen({
6187
6398
  {
6188
6399
  key: "l",
6189
6400
  label: isLiked ? HEART_FILLED2 : HEART_EMPTY2,
6190
- activeColor: isJustLiked ? colors.success : isLiked ? colors.error : undefined,
6401
+ activeColor: isJustLiked ? colors.success : isLiked ? colors.liked : undefined,
6191
6402
  isActive: isLiked || isJustLiked
6192
6403
  },
6193
6404
  {
6194
6405
  key: "b",
6195
6406
  label: isBookmarked ? FLAG_FILLED2 : FLAG_EMPTY2,
6196
- activeColor: isJustBookmarked ? colors.success : isBookmarked ? colors.primary : undefined,
6407
+ activeColor: isJustBookmarked ? colors.success : isBookmarked ? colors.bookmarked : undefined,
6197
6408
  isActive: isBookmarked || isJustBookmarked
6198
6409
  },
6199
6410
  { key: "p", label: "profile" },
@@ -6223,7 +6434,7 @@ function PostDetailScreen({
6223
6434
  activeColor: isLoadingQuote ? colors.primary : undefined,
6224
6435
  show: hasQuote
6225
6436
  },
6226
- { key: "m", label: "folder", show: isBookmarked && !hasMentions }
6437
+ { key: "f", label: "folder", show: isBookmarked }
6227
6438
  ];
6228
6439
  return /* @__PURE__ */ jsxDEV24("box", {
6229
6440
  style: { flexDirection: "column", height: "100%" },
@@ -6257,91 +6468,7 @@ function PostDetailScreen({
6257
6468
 
6258
6469
  // src/screens/ProfileScreen.tsx
6259
6470
  import { useKeyboard as useKeyboard14 } from "@opentui/react";
6260
- import { useState as useState21, useCallback as useCallback15, useEffect as useEffect19 } from "react";
6261
-
6262
- // src/hooks/useUserProfile.ts
6263
- import { useState as useState20, useEffect as useEffect18, useCallback as useCallback14, useRef as useRef11 } from "react";
6264
- function useUserProfile({
6265
- client,
6266
- username,
6267
- isSelf = false
6268
- }) {
6269
- const [user, setUser] = useState20(null);
6270
- const [tweets, setTweets] = useState20([]);
6271
- const [loading, setLoading] = useState20(true);
6272
- const [error, setError] = useState20(null);
6273
- const [refreshCounter, setRefreshCounter] = useState20(0);
6274
- const [likedTweets, setLikedTweets] = useState20([]);
6275
- const [likesLoading, setLikesLoading] = useState20(false);
6276
- const [likesError, setLikesError] = useState20(null);
6277
- const [likesFetched, setLikesFetched] = useState20(false);
6278
- const likesFetchInProgress = useRef11(false);
6279
- const fetchProfile = useCallback14(async () => {
6280
- setLoading(true);
6281
- setError(null);
6282
- const profileResult = await client.getUserByScreenName(username);
6283
- if (!profileResult.success) {
6284
- setError(profileResult.error ?? "Failed to load profile");
6285
- setLoading(false);
6286
- return;
6287
- }
6288
- const userProfile = profileResult.user;
6289
- if (!userProfile) {
6290
- setError("User not found");
6291
- setLoading(false);
6292
- return;
6293
- }
6294
- setUser(userProfile);
6295
- const tweetsResult = await client.getUserTweets(userProfile.id, 20);
6296
- if (tweetsResult.success) {
6297
- setTweets(tweetsResult.tweets ?? []);
6298
- } else {
6299
- setTweets([]);
6300
- }
6301
- setLoading(false);
6302
- }, [client, username]);
6303
- const fetchLikes = useCallback14(async () => {
6304
- if (!isSelf || likesFetchInProgress.current)
6305
- return;
6306
- likesFetchInProgress.current = true;
6307
- setLikesLoading(true);
6308
- setLikesError(null);
6309
- const likesResult = await client.getLikes(20);
6310
- if (likesResult.success) {
6311
- setLikedTweets(likesResult.tweets ?? []);
6312
- setLikesFetched(true);
6313
- } else {
6314
- setLikesError(likesResult.error ?? "Failed to load likes");
6315
- }
6316
- setLikesLoading(false);
6317
- likesFetchInProgress.current = false;
6318
- }, [client, isSelf]);
6319
- useEffect18(() => {
6320
- setUser(null);
6321
- setTweets([]);
6322
- setLikedTweets([]);
6323
- setLikesFetched(false);
6324
- setLikesError(null);
6325
- fetchProfile();
6326
- }, [fetchProfile, refreshCounter]);
6327
- const refresh = useCallback14(() => {
6328
- setRefreshCounter((prev) => prev + 1);
6329
- }, []);
6330
- return {
6331
- user,
6332
- tweets,
6333
- loading,
6334
- error,
6335
- refresh,
6336
- likedTweets,
6337
- likesLoading,
6338
- likesError,
6339
- fetchLikes,
6340
- likesFetched
6341
- };
6342
- }
6343
-
6344
- // src/screens/ProfileScreen.tsx
6471
+ import { useState as useState21, useCallback as useCallback14, useEffect as useEffect18 } from "react";
6345
6472
  init_media();
6346
6473
  import { jsxDEV as jsxDEV25, Fragment as Fragment2 } from "@opentui/react/jsx-dev-runtime";
6347
6474
  function formatJoinDate(createdAt) {
@@ -6386,15 +6513,15 @@ function ProfileScreen({
6386
6513
  const {
6387
6514
  user,
6388
6515
  tweets,
6389
- loading,
6516
+ isLoading,
6390
6517
  error,
6391
6518
  refresh,
6392
6519
  likedTweets,
6393
- likesLoading,
6520
+ isLikesLoading,
6394
6521
  likesError,
6395
6522
  fetchLikes,
6396
6523
  likesFetched
6397
- } = useUserProfile({
6524
+ } = useProfileQuery({
6398
6525
  client,
6399
6526
  username,
6400
6527
  isSelf
@@ -6405,7 +6532,7 @@ function ProfileScreen({
6405
6532
  });
6406
6533
  const [isFollowing, setIsFollowing] = useState21(false);
6407
6534
  const [isMuting, setIsMuting] = useState21(false);
6408
- useEffect19(() => {
6535
+ useEffect18(() => {
6409
6536
  if (user) {
6410
6537
  setIsFollowing(user.following ?? false);
6411
6538
  setIsMuting(user.muting ?? false);
@@ -6415,16 +6542,16 @@ function ProfileScreen({
6415
6542
  const [isCollapsed, setIsCollapsed] = useState21(false);
6416
6543
  const [mentionsMode, setMentionsMode] = useState21(false);
6417
6544
  const [mentionIndex, setMentionIndex] = useState21(0);
6418
- useEffect19(() => {
6419
- if (isSelf && activeTab === "likes" && !likesFetched && !likesLoading) {
6545
+ useEffect18(() => {
6546
+ if (isSelf && activeTab === "likes" && !likesFetched && !isLikesLoading) {
6420
6547
  fetchLikes();
6421
6548
  }
6422
- }, [isSelf, activeTab, likesFetched, likesLoading, fetchLikes]);
6549
+ }, [isSelf, activeTab, likesFetched, isLikesLoading, fetchLikes]);
6423
6550
  const bioMentions = user?.description ? extractMentions(user.description) : [];
6424
6551
  const hasMentions = bioMentions.length > 0;
6425
6552
  const mentionCount = bioMentions.length;
6426
6553
  const currentMention = hasMentions ? bioMentions[mentionIndex] : undefined;
6427
- const handleSelectedIndexChange = useCallback15((index) => {
6554
+ const handleSelectedIndexChange = useCallback14((index) => {
6428
6555
  setIsCollapsed(index > 0);
6429
6556
  }, []);
6430
6557
  useKeyboard14((key) => {
@@ -6508,6 +6635,12 @@ function ProfileScreen({
6508
6635
  setIsCollapsed(false);
6509
6636
  }
6510
6637
  break;
6638
+ case "tab":
6639
+ if (isSelf) {
6640
+ setActiveTab((prev) => prev === "tweets" ? "likes" : "tweets");
6641
+ setIsCollapsed(false);
6642
+ }
6643
+ break;
6511
6644
  case "f":
6512
6645
  if (!isSelf && user) {
6513
6646
  const newFollowing = !isFollowing;
@@ -6897,7 +7030,7 @@ function ProfileScreen({
6897
7030
  children: "[2] Likes"
6898
7031
  }, undefined, false, undefined, this) : " 2 Likes"
6899
7032
  }, undefined, false, undefined, this),
6900
- activeTab === "likes" && likesLoading && /* @__PURE__ */ jsxDEV25("text", {
7033
+ activeTab === "likes" && isLikesLoading && /* @__PURE__ */ jsxDEV25("text", {
6901
7034
  fg: colors.muted,
6902
7035
  children: " (loading...)"
6903
7036
  }, undefined, false, undefined, this)
@@ -6946,7 +7079,7 @@ function ProfileScreen({
6946
7079
  { key: "x", label: "x.com" },
6947
7080
  { key: "r", label: "refresh" }
6948
7081
  ];
6949
- if (loading) {
7082
+ if (isLoading) {
6950
7083
  return /* @__PURE__ */ jsxDEV25("box", {
6951
7084
  style: { flexDirection: "column", height: "100%" },
6952
7085
  children: /* @__PURE__ */ jsxDEV25("box", {
@@ -7044,7 +7177,7 @@ function ProfileScreen({
7044
7177
  }
7045
7178
 
7046
7179
  // src/screens/SplashScreen.tsx
7047
- import { useEffect as useEffect20, useState as useState22 } from "react";
7180
+ import { useEffect as useEffect19, useState as useState22 } from "react";
7048
7181
  import { jsxDEV as jsxDEV26 } from "@opentui/react/jsx-dev-runtime";
7049
7182
  var LOGO = [
7050
7183
  " \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
@@ -7058,7 +7191,7 @@ var SPINNER_FRAMES = ["\u25E2", "\u25E3", "\u25E4", "\u25E5"];
7058
7191
  var SPINNER_INTERVAL_MS = 100;
7059
7192
  function SplashScreen() {
7060
7193
  const [spinnerIndex, setSpinnerIndex] = useState22(0);
7061
- useEffect20(() => {
7194
+ useEffect19(() => {
7062
7195
  const interval = setInterval(() => {
7063
7196
  setSpinnerIndex((prev) => (prev + 1) % SPINNER_FRAMES.length);
7064
7197
  }, SPINNER_INTERVAL_MS);
@@ -7194,7 +7327,7 @@ function App({ client, user }) {
7194
7327
  }
7195
7328
  function AppContent({ client, user }) {
7196
7329
  const renderer = useRenderer2();
7197
- const { currentView, navigate, goBack, cycleNext, isMainView } = useNavigation({
7330
+ const { currentView, navigate, goBack, isMainView } = useNavigation({
7198
7331
  initialView: "timeline",
7199
7332
  mainViews: MAIN_VIEWS
7200
7333
  });
@@ -7206,7 +7339,7 @@ function AppContent({ client, user }) {
7206
7339
  const [actionMessage, setActionMessage] = useState23(null);
7207
7340
  const [showFooter, setShowFooter] = useState23(true);
7208
7341
  const { openModal, closeModal, isModalOpen } = useModal();
7209
- useEffect21(() => {
7342
+ useEffect20(() => {
7210
7343
  client.setOnSessionExpired(() => {
7211
7344
  openModal("session-expired", {});
7212
7345
  });
@@ -7224,7 +7357,7 @@ function AppContent({ client, user }) {
7224
7357
  bookmarkMutation,
7225
7358
  currentFolderId: selectedBookmarkFolder?.id
7226
7359
  });
7227
- useEffect21(() => {
7360
+ useEffect20(() => {
7228
7361
  if (actionMessage) {
7229
7362
  const timer = setTimeout(() => setActionMessage(null), 3000);
7230
7363
  return () => clearTimeout(timer);
@@ -7233,21 +7366,21 @@ function AppContent({ client, user }) {
7233
7366
  const [showSplash, setShowSplash] = useState23(true);
7234
7367
  const [minTimeElapsed, setMinTimeElapsed] = useState23(false);
7235
7368
  const initialLoadComplete = useRef12(false);
7236
- useEffect21(() => {
7369
+ useEffect20(() => {
7237
7370
  const timer = setTimeout(() => {
7238
7371
  setMinTimeElapsed(true);
7239
7372
  }, SPLASH_MIN_DISPLAY_MS);
7240
7373
  return () => clearTimeout(timer);
7241
7374
  }, []);
7242
- useEffect21(() => {
7375
+ useEffect20(() => {
7243
7376
  if (minTimeElapsed && initialLoadComplete.current) {
7244
7377
  setShowSplash(false);
7245
7378
  }
7246
7379
  }, [minTimeElapsed]);
7247
- const handlePostCountChange = useCallback16((count) => {
7380
+ const handlePostCountChange = useCallback15((count) => {
7248
7381
  setPostCount(count);
7249
7382
  }, []);
7250
- const handleInitialLoadComplete = useCallback16(() => {
7383
+ const handleInitialLoadComplete = useCallback15(() => {
7251
7384
  if (!initialLoadComplete.current) {
7252
7385
  initialLoadComplete.current = true;
7253
7386
  if (minTimeElapsed) {
@@ -7255,16 +7388,16 @@ function AppContent({ client, user }) {
7255
7388
  }
7256
7389
  }
7257
7390
  }, [minTimeElapsed]);
7258
- const handleBookmarkCountChange = useCallback16((count) => {
7391
+ const handleBookmarkCountChange = useCallback15((count) => {
7259
7392
  setBookmarkCount(count);
7260
7393
  }, []);
7261
- const handleBookmarkHasMoreChange = useCallback16((hasMore) => {
7394
+ const handleBookmarkHasMoreChange = useCallback15((hasMore) => {
7262
7395
  setBookmarkHasMore(hasMore);
7263
7396
  }, []);
7264
- const handleNotificationCountChange = useCallback16((count) => {
7397
+ const handleNotificationCountChange = useCallback15((count) => {
7265
7398
  setNotificationCount(count);
7266
7399
  }, []);
7267
- const handleUnreadCountChange = useCallback16((count) => {
7400
+ const handleUnreadCountChange = useCallback15((count) => {
7268
7401
  setUnreadNotificationCount(count);
7269
7402
  }, []);
7270
7403
  const [postStack, setPostStack] = useState23([]);
@@ -7272,17 +7405,17 @@ function AppContent({ client, user }) {
7272
7405
  const [isLoadingQuote, setIsLoadingQuote] = useState23(false);
7273
7406
  const [isLoadingParent, setIsLoadingParent] = useState23(false);
7274
7407
  const [threadRootTweet, setThreadRootTweet] = useState23(null);
7275
- const handlePostSelect = useCallback16((post) => {
7408
+ const handlePostSelect = useCallback15((post) => {
7276
7409
  setPostStack((prev) => [...prev, post]);
7277
7410
  initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
7278
7411
  navigate("post-detail");
7279
7412
  }, [navigate, initState]);
7280
- const handlePostSelectFromThread = useCallback16((post) => {
7413
+ const handlePostSelectFromThread = useCallback15((post) => {
7281
7414
  setPostStack((prev) => [...prev, post]);
7282
7415
  initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
7283
7416
  navigate("post-detail");
7284
7417
  }, [navigate, initState]);
7285
- const handleQuoteSelect = useCallback16(async (quotedTweet) => {
7418
+ const handleQuoteSelect = useCallback15(async (quotedTweet) => {
7286
7419
  if (isLoadingQuote)
7287
7420
  return;
7288
7421
  if (postStack.some((p) => p.id === quotedTweet.id)) {
@@ -7303,7 +7436,7 @@ function AppContent({ client, user }) {
7303
7436
  setIsLoadingQuote(false);
7304
7437
  }
7305
7438
  }, [client, initState, isLoadingQuote, navigate, postStack]);
7306
- const handleParentSelect = useCallback16(async (parentTweet) => {
7439
+ const handleParentSelect = useCallback15(async (parentTweet) => {
7307
7440
  if (isLoadingParent)
7308
7441
  return;
7309
7442
  const parentIndex = postStack.findIndex((p) => p.id === parentTweet.id);
@@ -7329,36 +7462,36 @@ function AppContent({ client, user }) {
7329
7462
  setIsLoadingParent(false);
7330
7463
  }
7331
7464
  }, [client, goBack, initState, isLoadingParent, navigate, postStack]);
7332
- const handleBackFromDetail = useCallback16(() => {
7465
+ const handleBackFromDetail = useCallback15(() => {
7333
7466
  goBack();
7334
7467
  setPostStack((prev) => prev.slice(0, -1));
7335
7468
  }, [goBack]);
7336
7469
  const [profileStack, setProfileStack] = useState23([]);
7337
7470
  const profileUsername = profileStack[profileStack.length - 1] ?? null;
7338
- const handleProfileOpen = useCallback16((username) => {
7471
+ const handleProfileOpen = useCallback15((username) => {
7339
7472
  setProfileStack((prev) => [...prev, username]);
7340
7473
  navigate("profile");
7341
7474
  }, [navigate]);
7342
- const handleThreadView = useCallback16(() => {
7475
+ const handleThreadView = useCallback15(() => {
7343
7476
  if (selectedPost) {
7344
7477
  setThreadRootTweet(selectedPost);
7345
7478
  navigate("thread");
7346
7479
  }
7347
7480
  }, [navigate, selectedPost]);
7348
- const handleBackFromThread = useCallback16(() => {
7481
+ const handleBackFromThread = useCallback15(() => {
7349
7482
  goBack();
7350
7483
  setThreadRootTweet(null);
7351
7484
  }, [goBack]);
7352
- const handleBackFromProfile = useCallback16(() => {
7485
+ const handleBackFromProfile = useCallback15(() => {
7353
7486
  goBack();
7354
7487
  setProfileStack((prev) => prev.slice(0, -1));
7355
7488
  }, [goBack]);
7356
- const handlePostSelectFromProfile = useCallback16((post) => {
7489
+ const handlePostSelectFromProfile = useCallback15((post) => {
7357
7490
  setPostStack((prev) => [...prev, post]);
7358
7491
  initState(post.id, post.favorited ?? false, post.bookmarked ?? false);
7359
7492
  navigate("post-detail");
7360
7493
  }, [navigate, initState]);
7361
- const handleMoveToFolder = useCallback16(() => {
7494
+ const handleMoveToFolder = useCallback15(() => {
7362
7495
  if (!selectedPost)
7363
7496
  return;
7364
7497
  openModal("folder-picker", {
@@ -7376,7 +7509,7 @@ function AppContent({ client, user }) {
7376
7509
  onClose: closeModal
7377
7510
  });
7378
7511
  }, [client, selectedPost, openModal, closeModal]);
7379
- const handleBookmarkFolderSelectorOpen = useCallback16(() => {
7512
+ const handleBookmarkFolderSelectorOpen = useCallback15(() => {
7380
7513
  openModal("bookmark-folder-selector", {
7381
7514
  client,
7382
7515
  currentFolder: selectedBookmarkFolder,
@@ -7387,7 +7520,7 @@ function AppContent({ client, user }) {
7387
7520
  onClose: closeModal
7388
7521
  });
7389
7522
  }, [client, selectedBookmarkFolder, openModal, closeModal]);
7390
- const handleCreateBookmarkFolder = useCallback16(() => {
7523
+ const handleCreateBookmarkFolder = useCallback15(() => {
7391
7524
  openModal("folder-name-input", {
7392
7525
  mode: "create",
7393
7526
  onSubmit: async (name) => {
@@ -7402,7 +7535,7 @@ function AppContent({ client, user }) {
7402
7535
  onClose: closeModal
7403
7536
  });
7404
7537
  }, [client, openModal, closeModal]);
7405
- const handleEditBookmarkFolder = useCallback16(() => {
7538
+ const handleEditBookmarkFolder = useCallback15(() => {
7406
7539
  if (!selectedBookmarkFolder)
7407
7540
  return;
7408
7541
  openModal("folder-name-input", {
@@ -7421,7 +7554,7 @@ function AppContent({ client, user }) {
7421
7554
  onClose: closeModal
7422
7555
  });
7423
7556
  }, [client, selectedBookmarkFolder, openModal, closeModal]);
7424
- const handleDeleteBookmarkFolder = useCallback16(() => {
7557
+ const handleDeleteBookmarkFolder = useCallback15(() => {
7425
7558
  if (!selectedBookmarkFolder)
7426
7559
  return;
7427
7560
  openModal("delete-folder-confirm", {
@@ -7439,7 +7572,7 @@ function AppContent({ client, user }) {
7439
7572
  onCancel: closeModal
7440
7573
  });
7441
7574
  }, [client, selectedBookmarkFolder, openModal, closeModal]);
7442
- const handleNotificationSelect = useCallback16((notification) => {
7575
+ const handleNotificationSelect = useCallback15((notification) => {
7443
7576
  if (notification.targetTweet) {
7444
7577
  setPostStack((prev) => [...prev, notification.targetTweet]);
7445
7578
  initState(notification.targetTweet.id, notification.targetTweet.favorited ?? false, notification.targetTweet.bookmarked ?? false);
@@ -7526,11 +7659,32 @@ function AppContent({ client, user }) {
7526
7659
  setShowFooter((prev) => !prev);
7527
7660
  return;
7528
7661
  }
7529
- if (showSplash || !isMainView) {
7662
+ if (showSplash) {
7663
+ return;
7664
+ }
7665
+ if (key.name === "1") {
7666
+ setPostStack([]);
7667
+ setProfileStack([]);
7668
+ setThreadRootTweet(null);
7669
+ navigate("timeline");
7670
+ return;
7671
+ }
7672
+ if (key.name === "2") {
7673
+ setPostStack([]);
7674
+ setProfileStack([]);
7675
+ setThreadRootTweet(null);
7676
+ navigate("bookmarks");
7530
7677
  return;
7531
7678
  }
7532
- if (key.name === "tab") {
7533
- cycleNext();
7679
+ if (key.name === "3") {
7680
+ setPostStack([]);
7681
+ setProfileStack([]);
7682
+ setThreadRootTweet(null);
7683
+ navigate("notifications");
7684
+ return;
7685
+ }
7686
+ if (!isMainView) {
7687
+ return;
7534
7688
  }
7535
7689
  if (key.name === "n") {
7536
7690
  navigate("notifications");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xfeed",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Terminal-based X/Twitter viewer with vim-style navigation. Install with: bun install -g xfeed",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -18,8 +18,8 @@
18
18
  "prepare": "lefthook install"
19
19
  },
20
20
  "dependencies": {
21
- "@opentui/core": "^0.1.68",
22
- "@opentui/react": "^0.1.68",
21
+ "@opentui/core": "^0.1.69",
22
+ "@opentui/react": "^0.1.69",
23
23
  "@steipete/sweet-cookie": "file:./vendor/sweet-cookie",
24
24
  "@tanstack/react-query": "^5.90.16",
25
25
  "@tanstack/react-router": "^1.144.0",