Skip to content

Comments

feat: add 'load more' on remaining pages#47

Merged
scanash00 merged 9 commits intomargin-at:mainfrom
hacdias:feat/44
Feb 16, 2026
Merged

feat: add 'load more' on remaining pages#47
scanash00 merged 9 commits intomargin-at:mainfrom
hacdias:feat/44

Conversation

@hacdias
Copy link
Contributor

@hacdias hacdias commented Feb 15, 2026

Built on top of #43.

Reuses some components, through which closes #44 :)

Summary by CodeRabbit

  • Documentation

    • Updated setup instructions to use bun package manager instead of npm
  • New Features

    • Added Annotations navigation item to mobile menu for authenticated users
    • Added preference option to disable external link warnings
  • Improvements

    • Refactored feed component rendering for enhanced consistency
    • Enhanced profile page layout and content display
    • Added horizontal scrolling support for navigation tabs

@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new FeedItems component to unify feed rendering with pagination across multiple pages, updates the feed API response shape to remove cursor and enforce pagination fields, adds unread notification tracking to mobile navigation, and updates the build setup to use bun. A DisableExternalLinkWarning field is added to the backend preferences.

Changes

Cohort / File(s) Summary
Feed Items Component
web/src/components/feed/FeedItems.tsx
New React component handling feed fetching, pagination, filtering, and rendering in both list and mosaic layouts. Manages loading states, empty states, item deletion, and load-more functionality with approximately 158 lines of logic.
Feed API and Types
web/src/api/client.ts, web/src/types.ts
Exports GetFeedParams interface, removes cursor from getFeed response, adds explicit AnnotationItem[] typing. FeedResponse interface now requires hasMore and fetchCount fields (removes optional cursor).
Feed Views Integration
web/src/views/core/Feed.tsx, web/src/views/profile/Profile.tsx, web/src/components/feed/MasonryFeed.tsx
Replaces internal feed content logic with new FeedItems component across multiple pages; consolidates pagination and loading state management; passes filter params (motivation, tag, creator) to FeedItems.
Navigation and UI
web/src/components/navigation/MobileNav.tsx, web/src/components/ui/Tabs.tsx
Adds unread notification count fetching and Annotations menu item to mobile navigation (when authenticated); enables horizontal scrolling for Tabs component.
Configuration and Minor Updates
README.md, backend/internal/firehose/ingester.go, web/src/lib/og.ts, web/src/pages/og-image.ts
Replaces npm with bun commands in development setup instructions; adds DisableExternalLinkWarning field to backend Preferences; formatting adjustments to description initialization and body text assignment.

Sequence Diagram(s)

sequenceDiagram
    participant App as Feed Component
    participant FeedItems
    participant API as API Client
    participant Server

    App->>FeedItems: Mount with filter params (creator, tag, motivation)
    activate FeedItems
    FeedItems->>FeedItems: Initialize state (items=[], offset=0, loading=true)
    FeedItems->>API: getFeed({...filters, limit: 50, offset: 0})
    activate API
    API->>Server: GET /feed?creator=...&tag=...&limit=50&offset=0
    Server-->>API: {items: [...], hasMore: bool, fetchCount: number}
    API-->>FeedItems: items[], hasMore, fetchCount
    deactivate API
    FeedItems->>FeedItems: setState({items, hasMore, offset: 0, loading: false})
    FeedItems->>App: Render items in list or mosaic layout
    deactivate FeedItems

    Note over FeedItems: User scrolls down, clicks Load More
    FeedItems->>FeedItems: loadMore() triggered
    FeedItems->>FeedItems: setState({loadingMore: true, offset += 50})
    FeedItems->>API: getFeed({...filters, limit: 50, offset: 50})
    activate API
    API->>Server: GET /feed?creator=...&tag=...&limit=50&offset=50
    Server-->>API: {items: [...], hasMore: bool, fetchCount: number}
    API-->>FeedItems: items[], hasMore, fetchCount
    deactivate API
    FeedItems->>FeedItems: setState({items: [...prev, ...new], hasMore, loadingMore: false})
    FeedItems->>App: Render updated items list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops through the feed with boundless grace,
Load more buttons find their place,
Pagination flows, items align,
In list and mosaic, oh how they shine,
A unified component, reusable and bright,
Feeds now unified, what a delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning While most changes support the primary goal, the PR includes several out-of-scope modifications: bun package manager migration, DisableExternalLinkWarning field addition, mobile navigation enhancements, and minor formatting/styling updates not directly related to adding load more functionality. Consider isolating load more implementation into a focused PR, moving unrelated changes (package manager updates, new preference fields, navigation features) to separate PRs or issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the PR: adding 'load more' functionality to remaining pages that currently lack it.
Linked Issues check ✅ Passed The PR successfully implements the requirements from issue #44 by creating a reusable FeedItems component and applying it across multiple feed pages.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/navigation/MobileNav.tsx (1)

36-42: ⚠️ Potential issue | 🟠 Major

Prevent stale unread badge when user identity changes.

The effect only depends on isAuthenticated, so switching accounts without a full unauth cycle (or rapid logout/login) can display the previous user’s unread badge until the fetch completes. This is a privacy leak and UX glitch. Reset the count on unauth or user change, and guard against stale async responses.

🛠️ Suggested fix (reset + guard + user identity dependency)
-  useEffect(() => {
-    if (isAuthenticated) {
-      getUnreadNotificationCount()
-        .then((count) => setUnreadCount(count || 0))
-        .catch(() => {});
-    }
-  }, [isAuthenticated]);
+  useEffect(() => {
+    let cancelled = false;
+    if (!isAuthenticated || !user?.did) {
+      setUnreadCount(0);
+      return;
+    }
+    setUnreadCount(0);
+    getUnreadNotificationCount()
+      .then((count) => {
+        if (!cancelled) setUnreadCount(count || 0);
+      })
+      .catch(() => {
+        if (!cancelled) setUnreadCount(0);
+      });
+    return () => {
+      cancelled = true;
+    };
+  }, [isAuthenticated, user?.did]);
🤖 Fix all issues with AI agents
In `@web/src/components/feed/MasonryFeed.tsx`:
- Around line 75-79: The emptyMessage ternary in MasonryFeed.tsx uses motivation
=== "bookmarking" ? "bookmarks" : "highlights", which mislabels "commenting" and
undefined; update the logic used when activeTab !== "my" to map motivation to
the correct noun (e.g., "bookmarking" -> "bookmarks", "commenting" ->
"annotations", default/undefined -> "annotations" or a generic
"annotations/highlights" per product rules). Implement this by replacing the
inline ternary with a small helper or switch (e.g.,
getLabelForMotivation(motivation)) and use that function in the emptyMessage
prop so the global tab shows the correct fallback text.
🧹 Nitpick comments (5)
README.md (1)

59-75: Add a brief Bun prerequisite note to avoid setup friction.

The commands now assume Bun is installed; a one-line prerequisite (e.g., “Install Bun first”) in the Development section would prevent confusion for new contributors.

web/src/lib/og.ts (1)

198-199: Minor style inconsistency with similar pattern.

The multi-line format here differs from the equivalent pattern in fetchAnnotationOG (line 130), which uses a single line for a similar chained || expression. Consider keeping the formatting consistent across these similar functions.

📝 Optional: align with single-line style
-  let description =
-    item.description || extractBody(item.body) || item.bodyValue || "";
+  let description = item.description || extractBody(item.body) || item.bodyValue || "";
web/src/views/profile/Profile.tsx (2)

81-81: Unused ref after refactor.

loadMoreTimerRef appears to be a remnant from the previous implementation. The cleanup effect at lines 179-183 still references it, but it's never populated since the load-more logic has been moved to FeedItems.

♻️ Suggested cleanup
-  const loadMoreTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

Also remove the cleanup effect:

-  useEffect(() => {
-    return () => {
-      if (loadMoreTimerRef.current) clearTimeout(loadMoreTimerRef.current);
-    };
-  }, []);

185-186: Duplicate computation of isHandle and resolvedDid.

These variables are computed at lines 185-186 and again inside the loadTabContent effect at lines 190-191. Consider removing the duplicate computation inside the effect and using the outer scope variables.

♻️ Suggested fix
   useEffect(() => {
     const loadTabContent = async () => {
-      const isHandle = !did.startsWith("did:");
-      const resolvedDid = isHandle ? profile?.did : did;
-
       if (!resolvedDid) return;

       setDataLoading(true);
web/src/components/feed/MasonryFeed.tsx (1)

1-1: Redundant import alias.

Both useStore and useNanoStore are imported, but they're the same function. The alias useNanoStore is used on line 23, while useStore is used on line 22. Consider using just one for consistency.

♻️ Suggested fix
-import { useStore as useNanoStore, useStore } from "@nanostores/react";
+import { useStore } from "@nanostores/react";

Then update line 23:

-  const layout = useNanoStore($feedLayout);
+  const layout = useStore($feedLayout);

Comment on lines +75 to +79
emptyMessage={
activeTab === "my"
? emptyMessage
: `No ${motivation === "bookmarking" ? "bookmarks" : "highlights"} from the community yet.`
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete emptyMessage fallback for global tab.

The ternary only handles "bookmarking" explicitly, defaulting everything else to "highlights". This could produce incorrect messages when motivation is "commenting" (would show "No highlights" instead of "No annotations") or undefined (would also show "No highlights").

🐛 Suggested fix
         emptyMessage={
           activeTab === "my"
             ? emptyMessage
-            : `No ${motivation === "bookmarking" ? "bookmarks" : "highlights"} from the community yet.`
+            : `No ${
+                motivation === "bookmarking"
+                  ? "bookmarks"
+                  : motivation === "commenting"
+                    ? "annotations"
+                    : motivation === "highlighting"
+                      ? "highlights"
+                      : "items"
+              } from the community yet.`
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emptyMessage={
activeTab === "my"
? emptyMessage
: `No ${motivation === "bookmarking" ? "bookmarks" : "highlights"} from the community yet.`
}
emptyMessage={
activeTab === "my"
? emptyMessage
: `No ${
motivation === "bookmarking"
? "bookmarks"
: motivation === "commenting"
? "annotations"
: motivation === "highlighting"
? "highlights"
: "items"
} from the community yet.`
}
🤖 Prompt for AI Agents
In `@web/src/components/feed/MasonryFeed.tsx` around lines 75 - 79, The
emptyMessage ternary in MasonryFeed.tsx uses motivation === "bookmarking" ?
"bookmarks" : "highlights", which mislabels "commenting" and undefined; update
the logic used when activeTab !== "my" to map motivation to the correct noun
(e.g., "bookmarking" -> "bookmarks", "commenting" -> "annotations",
default/undefined -> "annotations" or a generic "annotations/highlights" per
product rules). Implement this by replacing the inline ternary with a small
helper or switch (e.g., getLabelForMotivation(motivation)) and use that function
in the emptyMessage prop so the global tab shows the correct fallback text.

@scanash00 scanash00 merged commit 40c34f3 into margin-at:main Feb 16, 2026
1 check passed
@hacdias hacdias deleted the feat/44 branch February 16, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FR: 'Load More' on /bookmarks, /annotations and /highlights

2 participants