Skip to content

fix(replies): add cursor pagination for doubt replies - #1076

Open
Shreya-nipunge wants to merge 5 commits into
knoxiboy:mainfrom
Shreya-nipunge:fix-1062-reply-pagination
Open

fix(replies): add cursor pagination for doubt replies#1076
Shreya-nipunge wants to merge 5 commits into
knoxiboy:mainfrom
Shreya-nipunge:fix-1062-reply-pagination

Conversation

@Shreya-nipunge

@Shreya-nipunge Shreya-nipunge commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

User description

Description

This PR adds cursor-based pagination to the replies API to prevent unbounded database queries when loading doubt threads with a large number of replies.

Previously, the API returned the entire reply history in a single request, which increased response size, memory usage, and page load time for highly active discussions.

Changes

  • Added cursor-based keyset pagination to GET /api/replies.
  • Supports cursor and limit query parameters (default: 20, maximum: 100).
  • Uses a stable (createdAt, id) keyset predicate to avoid offset drift while maintaining ascending chronological order.
  • Uses over-fetching (limit + 1) to determine hasMore without an additional COUNT query.
  • Updated frontend consumers to transparently fetch additional pages until all replies are loaded, preserving the existing user experience.
  • Updated API tests to validate the new paginated response format.

Related Issue

Closes #1062

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Documentation update (README, guides, comments)
  • Style / UI change (no logic change)
  • Code refactor (no behavior change)
  • Test addition or update
  • Breaking change (fix or feature that would cause existing functionality to change)

Screenshots (if UI change)

N/A (Backend/API change)

How Has This Been Tested?

  • Tested locally with npm run dev
  • Verified on mobile viewport (375px)
  • Verified on desktop viewport (1440px)

Additionally verified:

  • npx tsc --noEmit passes.
  • ESLint passes for the modified files.
  • Updated API tests pass.
  • Correct behavior verified for threads with 0, 5, 20, 21, and 200+ replies.
  • Stable ordering is preserved across paginated responses with no duplicates or skipped replies.

Checklist

  • I have tested my changes locally (npm run dev)
  • My code follows the existing code style (TypeScript, Tailwind, no any types)
  • I have not introduced unrelated changes (each PR should address one issue)
  • I have added comments where necessary
  • My branch is up to date with main
  • I have linked the related issue above
  • Screenshots are included (if this is a UI change)

Summary by CodeRabbit

  • New Features
    • Added cursor-based pagination for doubt replies.
    • Added a “Load More Replies” option with pagination status.
    • Improved AI solution retrieval by searching across multiple reply pages.
  • Bug Fixes
    • Upvote status is now calculated only for displayed replies.
    • Anonymous viewers no longer trigger unnecessary upvote lookups.
    • Improved loading error handling and cancellation to prevent stale updates.
  • Tests
    • Updated reply endpoint tests for pagination and related response changes.

CodeAnt-AI Description

Add paginated reply loading without changing the thread experience

What Changed

  • Reply requests now return up to 20 replies at a time with a cursor and a hasMore indicator, avoiding oversized responses for long discussions
  • Users can load additional replies from the thread with a “Load More Replies” button
  • Replies loaded in multiple pages remain chronologically ordered and are not duplicated
  • AI solution lookup continues across pages so solutions are found even in older replies
  • Stale or overlapping requests no longer replace replies for a different doubt, and duplicate initial loading is prevented
  • Reply API tests now cover the paginated response format and user upvote behavior

Impact

✅ Faster loading for long reply threads
✅ Fewer duplicate or missing replies
✅ Reliable AI solution display in older discussions

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Copilot AI review requested due to automatic review settings July 26, 2026 18:19
@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

@Shreya-nipunge is attempting to deploy a commit to the Karan Mani Tripathi 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@codeant-ai

codeant-ai Bot commented Jul 26, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Incremental review completed c84a5de Aug 09, 2026 · 09:15 09:15
✅ Reviewed your PR 5d63b39 Jul 29, 2026 · 18:52 18:54
✅ Reviewed your PR 71e1472 Jul 26, 2026 · 18:19 18:21

@codeant-ai

codeant-ai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added gssoc'26 GSSoC program issue level:advanced Advanced level task type:bug Bug fix type:docs Documentation update labels Jul 26, 2026
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The replies GET endpoint now supports cursor-based pagination and returns pagination metadata. Classroom views retrieve successive pages, while API tests update mocks and assertions for the nested response shape and page-scoped upvote state.

Changes

Reply pagination

Layer / File(s) Summary
Paginated replies API
src/app/api/replies/route.ts
The GET endpoint adds cursor and limit handling, applies ordered database pagination, scopes upvote checks to the current page, and returns replies, nextCursor, and hasMore.
Classroom pagination consumers
src/components/classroom/AskAIView.tsx, src/components/classroom/DoubtRepliesModal.tsx
Classroom views request successive pages, search or accumulate json.replies, handle cancellation in AskAIView, and stop when hasMore is false.
API response contract tests
src/__tests__/api/replies.test.ts
Mocks and assertions reflect the paginated response shape and reply timestamps while retaining anonymous-user upvote-query coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClassroomView
  participant RepliesAPI
  participant RepliesDatabase
  ClassroomView->>RepliesAPI: Request replies with doubtId, limit, and cursor
  RepliesAPI->>RepliesDatabase: Query ordered page with limit + 1
  RepliesDatabase-->>RepliesAPI: Reply rows and extra-row indicator
  RepliesAPI-->>ClassroomView: replies, nextCursor, and hasMore
  ClassroomView->>RepliesAPI: Request next page while hasMore is true
Loading

Possibly related PRs

Suggested labels: type:performance, quality:clean

Suggested reviewers: knoxiboy

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The API and frontend changes implement pagination, bounded page sizes, and on-demand loading required by issue #1062.
Out of Scope Changes check ✅ Passed The changes support pagination consumers, update related tests, and do not introduce unrelated functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding cursor-based pagination for doubt replies.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown

@coderabbitai review
@codeantai review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (2)
src/components/classroom/AskAIView.tsx (1)

91-146: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

No staleness guard if initialDoubt changes mid-fetch.

The do...while loop can now span several sequential requests (up to N pages) before resolving. If initialDoubt changes while a previous run is still in-flight, the stale run's setMessages([initialUserMsg, assistantMsg]) can resolve after the newer effect started and overwrite the current conversation with an answer for the wrong doubt. There's no cleanup function returned from this useEffect to guard against it.

♻️ Suggested guard
   useEffect(() => {
     if (initialDoubt) {
+      let cancelled = false;
       ...
       const fetchSolution = async () => {
         setIsLoading(true);
         try {
           ...
           do {
             ...
           } while (!solution && hasMore);
-          if (solution) {
+          if (solution && !cancelled) {
             ...
             setMessages([initialUserMsg, assistantMsg]);
           }
         } catch (err) {
           console.error("Error fetching solution for initial doubt:", err);
         } finally {
-          setIsLoading(false);
+          if (!cancelled) setIsLoading(false);
         }
       };

       void fetchSolution();
+      return () => { cancelled = true; };
     }
   }, [initialDoubt]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/AskAIView.tsx` around lines 91 - 146, Protect the
fetchSolution effect from stale asynchronous results when initialDoubt changes.
Add an effect-scoped cancellation or active flag with cleanup, check it before
applying fetched messages and loading state, and ensure the cleanup invalidates
prior runs so only the latest initialDoubt can call setMessages.
src/components/classroom/DoubtRepliesModal.tsx (1)

120-154: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fetch failures mid-pagination are silent — no error state, and partial results are discarded.

If any page request in the loop fails (!res.ok or JSON parse error), the function returns immediately: no toast/error UI is shown (unlike handlePost, handleVote, etc. elsewhere in this component, which do surface toast.error), and any replies already accumulated from earlier successful pages in allReplies are dropped instead of being shown via setReplies(allReplies). Users get an indefinite/incomplete thread with no indication anything went wrong.

♻️ Suggested fix
             const res = await fetch(`/api/replies?${params}`);
             if (!res.ok) {
                 console.error(`Replies API failed with status ${res.status}`);
-                return;
+                toast.error("Failed to load replies.", { id: `replies-fetch-error-${doubt.id}` });
+                break;
             }
             let json;
             try {
                 json = await res.json();
             } catch (err) {
                 console.error("Failed to parse replies response:", err);
-                return;
+                toast.error("Failed to load replies.", { id: `replies-fetch-error-${doubt.id}` });
+                break;
             }
             allReplies = allReplies.concat(json.replies);
             cursor = json.nextCursor;
             hasMore = json.hasMore;
         } while (hasMore);

         setReplies(allReplies);

As per path instructions, "Missing loading/error states" should be reviewed for .tsx files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/DoubtRepliesModal.tsx` around lines 120 - 154,
Update fetchReplies to surface pagination failures through the component’s
existing toast.error pattern and preserve already fetched replies by calling
setReplies(allReplies) before exiting on non-OK responses or JSON parse errors.
Ensure the loading state still clears through the existing finally block and
avoid silently discarding partial results.

Source: Path instructions

🧹 Nitpick comments (3)
src/components/classroom/AskAIView.tsx (1)

117-121: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcoded string comparisons for solution detection.

"solution" and "DoubtDesk AI" are inline string literals used to identify AI-generated replies; consider extracting them to shared constants to avoid silent drift if the reply type enum or AI display name changes elsewhere.

As per path instructions, "No hardcoded strings (use constants)" for .tsx files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/AskAIView.tsx` around lines 117 - 121, Replace the
inline "solution" and "DoubtDesk AI" comparisons in the reply lookup within
AskAIView with shared constants, reusing existing definitions if available or
introducing appropriately named constants in the shared reply configuration.
Keep the solution-detection behavior unchanged while ensuring future enum or
display-name changes use a single source of truth.

Source: Path instructions

src/__tests__/api/replies.test.ts (1)

86-87: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding a tie-break ordering test.

These fixtures use distinct createdAt values, so they wouldn't surface the missing id tiebreaker in the route's orderBy (flagged separately in src/app/api/replies/route.ts). Consider adding a case with two replies sharing an identical createdAt and asserting the query builder was called with both asc(createdAt) and asc(id), or at least a stable ordering of the returned page.

Also applies to: 111-111, 123-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/api/replies.test.ts` around lines 86 - 87, Extend the replies
tests around the existing fixtures to include two replies with identical
createdAt values, then verify stable tie-break ordering by asserting the query
builder uses both asc(createdAt) and asc(id), or by asserting the returned page
is ordered consistently by id. Update the related cases at the referenced test
sections while preserving existing distinct-timestamp coverage.
src/components/classroom/DoubtRepliesModal.tsx (1)

122-148: 🚀 Performance & Scalability | 🔵 Trivial

Consider incremental loading rather than eagerly fetching every page.

fetchReplies (and AskAIView's solution search) now loops through every page before returning results, so for very active threads the client still accumulates the entire reply history in memory and the initial render is blocked until all pages complete — the per-request payload is bounded, but the round-trip count and end-to-end load time still scale with thread size. Since the linked issue calls out "Load More"/infinite scroll as the intended UX, worth considering surfacing the first page immediately and fetching subsequent pages on demand or in the background, if this hasn't been deferred intentionally for a later iteration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/DoubtRepliesModal.tsx` around lines 122 - 148,
Update fetchReplies in DoubtRepliesModal to expose the first replies page
immediately instead of awaiting every pagination request before setReplies.
Retain the nextCursor/hasMore state and fetch subsequent pages through an
on-demand or background load-more flow, appending results to the existing
replies without duplicating requests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/api/replies/route.ts`:
- Around line 105-117: The replies query’s ordering does not match its
`(createdAt, id)` keyset cursor. Update the `orderBy` in the replies fetch to
sort ascending by `repliesTable.createdAt` and then `repliesTable.id`,
preserving the existing limit and pagination logic.

---

Outside diff comments:
In `@src/components/classroom/AskAIView.tsx`:
- Around line 91-146: Protect the fetchSolution effect from stale asynchronous
results when initialDoubt changes. Add an effect-scoped cancellation or active
flag with cleanup, check it before applying fetched messages and loading state,
and ensure the cleanup invalidates prior runs so only the latest initialDoubt
can call setMessages.

In `@src/components/classroom/DoubtRepliesModal.tsx`:
- Around line 120-154: Update fetchReplies to surface pagination failures
through the component’s existing toast.error pattern and preserve already
fetched replies by calling setReplies(allReplies) before exiting on non-OK
responses or JSON parse errors. Ensure the loading state still clears through
the existing finally block and avoid silently discarding partial results.

---

Nitpick comments:
In `@src/__tests__/api/replies.test.ts`:
- Around line 86-87: Extend the replies tests around the existing fixtures to
include two replies with identical createdAt values, then verify stable
tie-break ordering by asserting the query builder uses both asc(createdAt) and
asc(id), or by asserting the returned page is ordered consistently by id. Update
the related cases at the referenced test sections while preserving existing
distinct-timestamp coverage.

In `@src/components/classroom/AskAIView.tsx`:
- Around line 117-121: Replace the inline "solution" and "DoubtDesk AI"
comparisons in the reply lookup within AskAIView with shared constants, reusing
existing definitions if available or introducing appropriately named constants
in the shared reply configuration. Keep the solution-detection behavior
unchanged while ensuring future enum or display-name changes use a single source
of truth.

In `@src/components/classroom/DoubtRepliesModal.tsx`:
- Around line 122-148: Update fetchReplies in DoubtRepliesModal to expose the
first replies page immediately instead of awaiting every pagination request
before setReplies. Retain the nextCursor/hasMore state and fetch subsequent
pages through an on-demand or background load-more flow, appending results to
the existing replies without duplicating requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 66c69207-91f6-4715-977c-5cb44325b257

📥 Commits

Reviewing files that changed from the base of the PR and between b5ab6a3 and 71e1472.

📒 Files selected for processing (4)
  • src/__tests__/api/replies.test.ts
  • src/app/api/replies/route.ts
  • src/components/classroom/AskAIView.tsx
  • src/components/classroom/DoubtRepliesModal.tsx

Comment thread src/app/api/replies/route.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
src/components/classroom/DoubtRepliesModal.tsx (2)

122-148: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Deduplicate the full pagination load.

fetchReplies is invoked by both effects at Lines 69 and 84. With this loop fetching every page, one modal open can issue the entire page sequence twice and race two setReplies updates. Consolidate the triggers or deduplicate/abort an in-flight request before adding pagination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/DoubtRepliesModal.tsx` around lines 122 - 148,
Prevent duplicate full-pagination loads from fetchReplies being triggered by
both effects; consolidate the effect triggers or cancel/reuse the existing
in-flight request before starting another. Ensure one modal open performs a
single pagination sequence and only its active request updates setReplies.

134-150: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not commit partial pagination results as a successful load.

When a later page fails, break is followed by setReplies(allReplies), so the modal displays an incomplete thread and count while only showing a transient toast. Retain the previous data or expose a persistent retry/error state, and commit the accumulated replies only after all pages load successfully.

As per path instructions, this TSX flow must provide a reliable loading/error state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/classroom/DoubtRepliesModal.tsx` around lines 134 - 150,
Update the pagination flow in DoubtRepliesModal so failures from later requests
or response parsing do not fall through to setReplies(allReplies). Track the
load as unsuccessful, preserve the existing replies (or expose a persistent
retry/error state), and call setReplies only after every page completes
successfully; ensure the loading/error state remains reliable.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/components/classroom/DoubtRepliesModal.tsx`:
- Around line 122-148: Prevent duplicate full-pagination loads from fetchReplies
being triggered by both effects; consolidate the effect triggers or cancel/reuse
the existing in-flight request before starting another. Ensure one modal open
performs a single pagination sequence and only its active request updates
setReplies.
- Around line 134-150: Update the pagination flow in DoubtRepliesModal so
failures from later requests or response parsing do not fall through to
setReplies(allReplies). Track the load as unsuccessful, preserve the existing
replies (or expose a persistent retry/error state), and call setReplies only
after every page completes successfully; ensure the loading/error state remains
reliable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5cead9ba-adca-4709-a61c-9d1e772d3a11

📥 Commits

Reviewing files that changed from the base of the PR and between 71e1472 and 3ef15a3.

📒 Files selected for processing (3)
  • src/app/api/replies/route.ts
  • src/components/classroom/AskAIView.tsx
  • src/components/classroom/DoubtRepliesModal.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/classroom/AskAIView.tsx
  • src/app/api/replies/route.ts

@knoxiboy knoxiboy added level:intermediate Intermediate level task and removed level:advanced Advanced level task labels Jul 29, 2026
@knoxiboy

knoxiboy commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Hi @Shreya-nipunge! Thanks for implementing cursor-based pagination for doubt replies (#1062).

The backend cursor encoding/decoding in src/app/api/replies/route.ts is implemented very cleanly! However, there is a frontend network loop issue in DoubtRepliesModal.tsx that defeats the purpose of pagination.

Problem Breakdown

In src/components/classroom/DoubtRepliesModal.tsx:

let allReplies: Reply[] = [];
let cursor: string | null = null;
let hasMore = false;

do {
    const params = new URLSearchParams({ doubtId: String(doubt.id) });
    params.set("limit", "100");
    if (cursor) params.set("cursor", cursor);

    const res = await fetch(`/api/replies?${params}`);
    ...
    allReplies = allReplies.concat(json.replies);
    cursor = json.nextCursor;
    hasMore = json.hasMore;
} while (hasMore);

Why this needs adjustment:

  1. Defeats Pagination Purpose: The do...while (hasMore) loop eagerly fetches every single page sequentially until hasMore === false. For long threads (e.g. 200+ replies), this fires multiple immediate HTTP requests and loads all items into memory at once when the modal opens.
  2. Performance & Rate Limiting: Eagerly fetching all batches can trigger API rate limits and cause UI lag.

Suggested Fix

In DoubtRepliesModal.tsx, fetch only the first page (e.g., limit 20) on modal open, and add a "Load More Replies" button at the bottom of the reply list to fetch subsequent batches on demand using nextCursor.

Thanks for your hard work on this optimization!

@codeant-ai

codeant-ai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Jul 29, 2026
@github-actions github-actions Bot added size/l and removed size/m size:L This PR changes 100-499 lines, ignoring generated files labels Jul 29, 2026
@Shreya-nipunge

Shreya-nipunge commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@knoxiboy ,Thanks for the feedback! I've updated the frontend pagination flow as suggested.

The modal now fetches only the first page on open (using the default page size) and stores nextCursor/hasMore in component state. Additional replies are loaded on demand through a Load More Replies button, which appends the next page using the existing cursor-based API.

This preserves the benefits of pagination by avoiding eager requests and unnecessary memory usage while keeping the existing reply functionality unchanged.

I've also verified that the changes pass TypeScript and ESLint checks.

Comment thread src/app/api/replies/route.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/classroom/DoubtRepliesModal.tsx`:
- Around line 180-182: Update the reply merge in DoubtRepliesModal so newly
fetched or posted replies are deduplicated by id before appending to the
existing replies state. Preserve the cursor and hasMore updates, and ensure each
reply appears only once so React keys remain unique.
- Around line 147-149: Update the reply-count state and its consumers in
DoubtRepliesModal so the header and tab badges do not present the paginated
replies.length as the total thread count. Prefer storing and using a total count
returned by the API alongside json.replies; otherwise relabel the displayed
values explicitly as loaded counts.
- Around line 779-784: Update the tie-breaker in the reply sorting comparator
around isPendingA and isPendingB to compare server reply IDs numerically rather
than using String(...).localeCompare(...). Preserve the API’s numeric ID
ordering and provide a fallback comparison for pending IDs that may not be
numeric.
- Around line 158-187: Update loadMoreReplies to associate each request with the
currently active doubt and pagination generation, using an AbortController or
request-generation token. Before applying json.replies, nextCursor, or hasMore,
ignore responses whose doubt.id or generation no longer matches the active modal
state, including requests superseded by a refresh or doubt switch; preserve
loading cleanup for the current request.
- Around line 125-149: Update the replies-loading flow in DoubtRepliesModal so
failed HTTP or JSON responses do not clear previously loaded replies or render
an empty thread. Add an explicit replies error state with retry UI, ensure
loading and error states are represented in the TSX, and only replace replies
with an empty array after a successful response that contains no replies.
- Around line 136-144: Extract the hardcoded error messages and reply-loading
button labels used by DoubtRepliesModal into the project’s existing string
constants or localization source, then replace the inline TSX strings with those
references throughout the component, including the locations around the response
parsing and reply-loading states.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 03562b4c-a3ba-44e9-8fa6-b4c16ce8f02c

📥 Commits

Reviewing files that changed from the base of the PR and between 3ef15a3 and 5d63b39.

📒 Files selected for processing (1)
  • src/components/classroom/DoubtRepliesModal.tsx

Comment thread src/components/classroom/DoubtRepliesModal.tsx
Comment thread src/components/classroom/DoubtRepliesModal.tsx
Comment thread src/components/classroom/DoubtRepliesModal.tsx
Comment thread src/components/classroom/DoubtRepliesModal.tsx
Comment thread src/components/classroom/DoubtRepliesModal.tsx Outdated
Comment thread src/components/classroom/DoubtRepliesModal.tsx Outdated

@knoxiboy knoxiboy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hi @Shreya-nipunge! Thanks for updating the reply pagination.

  • Please re-verify that \DoubtRepliesModal.tsx\ fetches strictly page 1 on open.
  • Note that \AskAIView.tsx\ still contains a \do { ... } while (!solution && hasMore)\ loop searching for the AI solution reply. While acceptable for solution lookup, please ensure it doesn't cause unnecessary network cascades on initial load.

…tion guards

- Remove redundant fetchReplies() call from loadPendingReplies effect
- Replace unconditional setReplies([]) with doubt-ID ref for stale guard
- Deduplicate Load More responses by reply ID
- Use numeric comparison for reply ID sorting
@codeant-ai

codeant-ai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 9, 2026
@github-actions github-actions Bot removed the size:L This PR changes 100-499 lines, ignoring generated files label Aug 9, 2026
@Shreya-nipunge

Copy link
Copy Markdown
Contributor Author

Hi @knoxiboy ,

Thanks for the clarification. I re-verified both points against the current implementation.

DoubtRepliesModal.tsx

  • The initial load now makes exactly one request for page 1 (doubtId, limit=20, no cursor).
  • The duplicate initial request was caused by loadPendingReplies() also calling fetchReplies(); that call has been removed.
  • fetchReplies() loads only one page and stores nextCursor/hasMore.
  • Subsequent pages are fetched only through Load More, one page per click.
  • The existing stale-request guard prevents responses from a previous doubt from being applied after switching doubts.
  • Existing replies are preserved if the initial or Load More request fails.
  • Load More responses are deduplicated by reply ID.

AskAIView.tsx

I also verified the do { ... } while (!solution && hasMore) loop.

It is specifically used to locate the AI solution reply across paginated results. It:

  • stops immediately when the solution reply is found,
  • stops when there are no more pages,
  • does not eagerly fetch the entire reply history,
  • is triggered by a single effect,
  • uses cleanup/cancellation to prevent stale responses.

So I did not make any changes to AskAIView.tsx.

Validation

  • TypeScript: ✅
  • ESLint: ✅
  • Replies pagination tests: ✅ 2/2

I also checked the failing CI jobs:

  • E2E: fails during drizzle-kit push while pulling the database schema, before the PR-specific E2E tests execute.
  • Unit tests: affected tests fail during module initialization because GROQ_API_KEY is unavailable in CI.
  • doubts-flag.test.ts: fails on the unrelated /api/doubts/flag endpoint.

No unrelated code or CI configuration was changed.

@knoxiboy knoxiboy added level:advanced Advanced level task type:feature New feature and removed level:intermediate Intermediate level task labels Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc'26 GSSoC program issue level:advanced Advanced level task review-needed size/l type:bug Bug fix type:docs Documentation update type:feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Doubt replies within a thread are fetched without pagination

3 participants