Skip to content

Fix(paper): improve saved table layout and track-scoped filtering#367

Merged
Linjie-top merged 7 commits into
jerry609:devfrom
Linjie-top:fix/paper
Mar 12, 2026
Merged

Fix(paper): improve saved table layout and track-scoped filtering#367
Linjie-top merged 7 commits into
jerry609:devfrom
Linjie-top:fix/paper

Conversation

@Linjie-top

Copy link
Copy Markdown
Collaborator

Summary

This PR refines the paper Saved page UX and fixes the track‑scoped filtering bug:

  • Title column no longer breaks the table layout for long paper titles.
  • Unsave now goes through the unified feedback API so items are actually removed.
  • Track filters (cv, llm, etc.) now show only papers that are currently saved in that track instead of “any track that ever
    saved it”.

Changes

1) Saved papers table layout (frontend)

File: web/src/components/research/SavedPapersList.tsx

  • Switched the table to fixed layout: Table className="table-fixed w-full".
  • Defined column widths:
    • Checkbox: w-[50px]
    • Title: w-[40%], left aligned
    • Source: w-[14%], left aligned
    • Saved: w-[16%], centered
    • Judge: w-[10%], centered
    • Status: w-[10%], centered
    • Actions: w-[10%], right aligned
  • Tightened the Title column:
    • Title text now uses truncate + title={paper.title} so long titles show a tooltip on hover instead of stretching the table.
    • Authors and venue remain as secondary lines underneath the title.
  • Made the middle columns visually cleaner:
    • Saved dates, Judge score+recommendation, and Status badge are centered for easier scanning.
    • Source column uses whitespace-nowrap to keep the badge compact.

效果:长标题不会再把 Source / Judge 等列挤乱,鼠标放上去还能看到完整论文题目。

2) Unified Unsave with feedback API (frontend)

File: web/src/components/research/SavedPapersList.tsx

  • Replaced the old library endpoint:

    DELETE /api/papers/{paperId}/save?user_id=default

which only worked when paper_feedback.paper_id was the internal numeric ID.

  • New behaviour uses the same feedback pipeline as the Research page:

    POST /api/research/papers/feedback
    {
    user_id: "default",
    track_id: selectedTrackId,
    paper_id: externalId || String(paperId),
    action: "unsave",
    ...
    }

    • paper_id prefers paper.external_url or paper.url (external identity), falling back to the numeric id string.
    • track_id comes from the current Saved view (e.g., llm or cv), so unsaving is track‑aware.
    • On success the row is removed from the Saved table.

This fixes the "Paper not in library" case where:

  • the paper was saved via /research/papers/feedback using an external ID (arxiv URL, etc.),
  • but the old unsave path tried to delete by numeric paper_id in the library table.

Now save & unsave always go through the same API and identity model.

3) Track‑scoped saved filtering (backend)

File: src/paperbot/infrastructure/stores/research_store.py

Method: list_saved_papers(...)

Previous behaviour:

  • For each paper, we built saved_track_membership[paper_id] by looking at all save feedback rows.
  • A track was considered to “own” a paper if it ever had a save action for that paper, regardless of later unsaves.
  • Combined with a global saved_at flag, this caused:
    • All Tracks: all globally saved papers (OK).
    • cv / llm views: any paper that was ever saved in that track and still globally saved → made cv sometimes look almost identical
      to All Tracks.

New behaviour:

  • Still reads all save / unsave rows for the user, ordered by descending timestamp.

  • Tracks effective state per paper/track pair:

    latest_per_track[(paper_ref_id, track_id)] = latest_action # save or unsave

    Only the first row in descending order is taken as the state for that (paper, track) pair.

  • Builds saved_track_membership only from entries where the effective action is save:

    for (pid, tid), action in latest_per_track.items():
    if action == "save":
    saved_track_membership[pid].add(tid)

  • Global saved_at_by_paper logic stays the same and is used for the All Tracks view.

As a result:

  • All Tracks: still shows all globally saved papers.
  • llm Saved view: only shows papers whose latest feedback in llm is save.
  • cv Saved view: only shows papers whose latest feedback in cv is save.
  • Unsaving in one track no longer leaks into the other track’s membership, and a paper doesn’t keep showing up in a track just
    because it was saved there once in history.

———

Testing

Local dev environment:

  • Backend:
    • Started uvicorn src.paperbot.api.main:app --reload --port 8000
  • Frontend:
    • cd web && npm run dev

Manual checks:

  • Long titles:
    • Verified that very long paper titles no longer stretch into the Source column.
    • Hovering title shows full text via tooltip.
  • Track filters:
    • In llm track, searched and saved several RAG papers.
    • In cv Saved view, those RAG papers do not show up unless they were explicitly saved in cv.
    • All Tracks still shows the full set of saved papers.
  • Unsave:
    • For papers saved via the Research page feedback (external IDs), clicking Unsave in the Saved table now removes the row without
      "Paper not in library" errors.
    • Verified that unsaving in llm only affects the llm Saved view; cv view remains unchanged unless that track also unsaved it.

———

Notes

  • No API shape changes; only internal filtering logic and frontend integration are adjusted.
  • The goal is to make Saved behaviour consistent with the existing feedback model and to make track‑scoped Saved views match user
    expectations.

@vercel

vercel Bot commented Mar 12, 2026

Copy link
Copy Markdown

@Linjie-top is attempting to deploy a commit to the Jerry's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1ab66221-dcf7-412b-b6ef-ff1990ab01ce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan for PR comments
  • Generate coding plan

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 and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the user experience on the "Saved" page by addressing layout issues and refining core functionalities. It ensures that the saved papers table maintains a clean and readable appearance, even with lengthy titles, and standardizes the "Unsave" operation through a unified feedback API. Crucially, it rectifies a bug in track-scoped filtering, providing users with an accurate view of papers currently saved within each specific track, aligning the system's behavior with user expectations.

Highlights

  • Improved Table Layout: The "Saved" papers table now has a fixed layout with defined column widths, preventing long paper titles from breaking the display. Long titles are truncated and show full text on hover.
  • Unified Unsave Functionality: The "Unsave" action now uses the unified feedback API, resolving issues where papers saved via external IDs could not be unsaved correctly.
  • Accurate Track-Scoped Filtering: The backend logic for track filters has been updated to reflect the current save state of papers within a specific track, ensuring that only actively saved papers for that track are displayed.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/paperbot/infrastructure/stores/research_store.py
    • Implemented new logic to track the latest effective save/unsave state for each paper globally and for each (paper, track) pair.
    • Modified the saved_track_membership to be populated based on these effective per-track states, ensuring that only papers with a current "save" action in a given track are included.
  • web/src/components/research/SavedPapersList.tsx
    • Applied table-fixed w-full to the Table component and defined specific w-[...] classes for TableHead elements to control column widths.
    • Updated the Title TableCell to use truncate and title={paper.title} for better handling of long titles.
    • Added whitespace-nowrap to the Source TableCell and text-center to Saved, Judge, and Status TableCell components for improved visual presentation.
    • Refactored the unsavePaper function to call the /api/research/papers/feedback endpoint with a POST request, including track_id and externalId for robust unsaving.
    • Imported currentFeedbackFromRequestAction, toggleSaveFeedbackAction, and PaperFeedbackRequestAction from lib/paper-feedback.
    • Introduced activeTrackId to capture the selectedTrackId for use in the unsavePaper payload.
Activity
  • The author performed manual testing in a local development environment, verifying the fixes for long titles, track filters, and the unsave functionality.
  • Confirmed that long paper titles no longer disrupt the table layout and display full text on hover.
  • Validated that track filters correctly scope saved papers, showing only those explicitly saved in the selected track.
  • Verified that unsaving papers, especially those saved via external IDs, now works without errors and correctly removes the row from the table.
  • Ensured that unsaving in one track does not inadvertently affect other track views.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@vercel

vercel Bot commented Mar 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
paper-bot Ready Ready Preview, Comment Mar 12, 2026 1:43pm

@Linjie-top
Linjie-top marked this pull request as ready for review March 12, 2026 10:48
@Linjie-top
Linjie-top requested a review from jerry609 as a code owner March 12, 2026 10:48

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces valuable improvements to the "Saved Papers" page, enhancing both user experience and backend logic. The frontend changes for table layout and the unification of the unsave action are well-implemented. The backend fix for track-scoped filtering is sound and correctly resolves the described issue. I've included a couple of suggestions for minor refactoring to enhance efficiency and code clarity.

Comment on lines +732 to +755
latest_per_track: Dict[tuple[int, int], str] = {}

for row in feedback_rows:
pid = int(row.paper_ref_id or 0)
if pid <= 0:
continue
normalized_action = self._normalize_feedback_action(str(row.action or ""))
if normalized_action == "save":
saved_track_membership[pid].add(int(row.track_id or 0))

# Per-track state: first row encountered in descending
# timestamp order is the effective state for that
# (paper, track) pair.
tid = int(row.track_id or 0)
key = (pid, tid)
if tid > 0 and key not in latest_per_track:
latest_per_track[key] = normalized_action

# Global latest save/unsave per paper (track-agnostic)
if pid not in latest_save_state:
latest_save_state[pid] = (normalized_action == "save", row.ts)

# Build membership mapping from effective per-track states.
for (pid, tid), action in latest_per_track.items():
if action == "save":
saved_track_membership[pid].add(tid)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The logic to build saved_track_membership can be made more efficient. Currently, you iterate through feedback_rows to populate latest_per_track, and then iterate through latest_per_track to build saved_track_membership. You can combine these into a single loop, which would avoid creating the intermediate latest_per_track dictionary and improve performance by reducing iterations.

            seen_per_track: set[tuple[int, int]] = set()

            for row in feedback_rows:
                pid = int(row.paper_ref_id or 0)
                if pid <= 0:
                    continue
                normalized_action = self._normalize_feedback_action(str(row.action or ""))

                # Per-track state: first row encountered in descending
                # timestamp order is the effective state for that
                # (paper, track) pair.
                tid = int(row.track_id or 0)
                key = (pid, tid)
                if tid > 0 and key not in seen_per_track:
                    seen_per_track.add(key)
                    if normalized_action == "save":
                        saved_track_membership[pid].add(tid)

                # Global latest save/unsave per paper (track-agnostic)
                if pid not in latest_save_state:
                    latest_save_state[pid] = (normalized_action == "save", row.ts)

setUpdatingAction(null)
}
}, [])
const data = await res.json()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The data constant is assigned the result of res.json(), but it's never used. This is dead code and can be removed for clarity.

@jerry609 jerry609 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.

1

@Linjie-top
Linjie-top merged commit 2cd524e into jerry609:dev Mar 12, 2026
6 of 7 checks passed
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.

2 participants