Skip to content

feat: redesign dashboard daily brief#355

Merged
jerry609 merged 2 commits into
masterfrom
pr/dashboard-daily-brief
Mar 12, 2026
Merged

feat: redesign dashboard daily brief#355
jerry609 merged 2 commits into
masterfrom
pr/dashboard-daily-brief

Conversation

@jerry609

@jerry609 jerry609 commented Mar 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • redesign /dashboard into a daily brief with hot picks, trend radar, and a decision rail
  • suppress body hydration warnings and restore the /workflows sidebar entry
  • refresh the README dashboard screenshot and add the dashboard intelligence dependencies needed on master

Validation

  • npx eslint src/app/layout.tsx src/app/dashboard/page.tsx src/components/layout/Sidebar.tsx src/components/dashboard/DashboardDailyBriefView.tsx src/lib/dashboard-api.ts src/lib/dashboard-intelligence.ts src/lib/dashboard-intelligence.test.ts src/lib/types.ts src/lib/dashboard-brief.ts src/lib/dashboard-brief.test.ts e2e/dashboard-workflow.spec.ts
  • npx tsc --noEmit
  • npm run test -- src/lib/dashboard-brief.test.ts src/lib/dashboard-intelligence.test.ts
  • E2E_BASE_URL=http://127.0.0.1:3000 npx playwright test e2e/dashboard-workflow.spec.ts --project=chromium

Notes

  • the dashboard screenshot was regenerated from the local app after restoring the sidebar navigation

Summary by CodeRabbit

  • New Features

    • Dashboard redesigned as "Daily Research Brief" with organized sections: daily brief, hot picks, trend radar, and decision rail for prioritized workflow management.
    • Added community intelligence signals integration to the dashboard for enhanced insights.
    • Full workflow console relocated to /workflows for improved navigation.
  • Chores

    • Removed font configuration from the layout.

Copilot AI review requested due to automatic review settings March 12, 2026 03:35
@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 3:36am

@coderabbitai

coderabbitai Bot commented Mar 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The PR refactors the dashboard from a complex composite layout into a streamlined daily brief view. It introduces intelligence feed fetching, builds structured daily briefs from data files, creates intelligence cards, and updates navigation. A new E2E test validates dashboard-to-workflows navigation.

Changes

Cohort / File(s) Summary
Documentation & Testing
README.md, web/e2e/dashboard-workflow.spec.ts
Added dashboard description and new E2E test validating navigation from /dashboard to /workflows via "打开完整工作台" link.
Dashboard Layout Refactor
web/src/app/dashboard/page.tsx, web/src/components/dashboard/DashboardDailyBriefView.tsx
Replaced large inline dashboard composition with DashboardDailyBriefView component. Introduced new data-fetching pipeline (fetchPapers, fetchLatestDashboardDailyBrief, fetchIntelligenceFeed) and derived metrics (trendCards, intelligenceSourceSummary). Refactored control flow and consolidated prop passing.
Dashboard Data Layer
web/src/lib/dashboard-api.ts, web/src/lib/dashboard-brief.ts, web/src/lib/dashboard-intelligence.ts, web/src/lib/dashboard-brief.test.ts, web/src/lib/dashboard-intelligence.test.ts
Added new functions and types: fetchIntelligenceFeed, buildDashboardDailyBrief, buildDashboardIntelligenceCards. Introduced data transformation utilities for daily brief highlights and intelligence cards. Includes comprehensive unit tests.
Type Definitions
web/src/lib/types.ts
Added IntelligenceFeedItem, IntelligenceFeedResponse, and IntelligenceMatchedTrack interfaces to model intelligence feed metadata and tracking.
Layout & Navigation
web/src/app/layout.tsx, web/src/components/layout/Sidebar.tsx
Removed Geist font imports and usage from root layout. Added Workflows icon and route entry in sidebar navigation.

Sequence Diagram

sequenceDiagram
    participant Client as Browser Client
    participant DashPage as Dashboard Page
    participant DataFetch as Data Layer
    participant FileSystem as File System
    participant APIs as External APIs
    participant DailyBrief as DashboardDailyBriefView

    Client->>DashPage: Load /dashboard
    DashPage->>DataFetch: fetchLatestDashboardDailyBrief()
    DataFetch->>FileSystem: resolveReportDirectory()
    FileSystem-->>DataFetch: report path
    DataFetch->>FileSystem: readLatestDailyReport()
    FileSystem-->>DataFetch: DailyReport JSON
    DataFetch->>DataFetch: buildDashboardDailyBrief(report)
    DataFetch-->>DashPage: DashboardDailyBrief
    
    DashPage->>DataFetch: fetchIntelligenceFeed(userId)
    DataFetch->>APIs: GET /intelligence-feed
    APIs-->>DataFetch: IntelligenceFeedResponse
    DataFetch->>DataFetch: buildDashboardIntelligenceCards(items)
    DataFetch-->>DashPage: DashboardIntelligenceCard[]
    
    DashPage->>DailyBrief: render with brief + intelligence data
    DailyBrief->>DailyBrief: render sections (greeting, tiles, papers, signals, decision rail)
    DailyBrief-->>Client: HTML (Daily Brief UI)
    
    Client->>DailyBrief: click "打开完整工作台"
    DailyBrief-->>Client: navigate to /workflows
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through dashboard halls,
Where daily briefs now grace the walls,
Intelligence feeds in signals shine,
Brief and bright in design so fine,
From workflows old to papers new,
This dashboard refresh hops right through!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: redesign dashboard daily brief' directly and clearly reflects the main change in the PR, which is a comprehensive redesign of the dashboard with a new daily brief layout featuring hot picks, trend radar, and decision rail.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr/dashboard-daily-brief

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 introduces a significant overhaul of the dashboard, transforming it into a focused daily brief. The new design aims to provide users with a concise overview of critical information, including key research highlights, emerging trends, and prioritized actions, thereby enhancing decision-making efficiency. This change centralizes essential daily insights while relocating more detailed workflow management to a dedicated section.

Highlights

  • Dashboard Redesign: The /dashboard page has been completely redesigned to function as a daily brief, featuring 'hot picks', a 'trend radar', and a 'decision rail' to streamline information and actions.
  • Workflow Console Relocation: The full workflow console has been moved back to /workflows, and its entry in the sidebar navigation has been restored to its original position.
  • Hydration Warning Suppression: Body hydration warnings have been suppressed in the main layout to prevent unnecessary console output.
  • Updated Documentation and Dependencies: The README.md file now includes an updated screenshot of the new dashboard layout, and necessary dashboard intelligence dependencies have been added for the master branch.
  • New E2E Test Coverage: A new end-to-end test has been added to verify the functionality and visibility of the redesigned dashboard daily brief.

🧠 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
  • README.md
    • Updated dashboard layout description to reflect the daily brief organization.
  • web/e2e/dashboard-workflow.spec.ts
    • Added new E2E test to validate the dashboard daily brief page and its navigation to the workflows page.
  • web/src/app/dashboard/page.tsx
    • Rewrote the dashboard page to integrate the new DashboardDailyBriefView component.
    • Implemented new data fetching and processing logic for hot papers, trend cards, action lanes, and destination cards.
    • Removed previous dashboard components and associated data fetching for a streamlined daily brief experience.
  • web/src/app/layout.tsx
    • Removed unused Geist font imports.
    • Added suppressHydrationWarning to the body tag to address hydration issues.
  • web/src/components/dashboard/DashboardDailyBriefView.tsx
    • Added a new React component to render the complete daily brief dashboard UI, including hot papers, trend radar, and decision rail sections.
  • web/src/components/layout/Sidebar.tsx
    • Reordered sidebar navigation items to place 'Workflows' after 'Research'.
  • web/src/lib/dashboard-api.ts
    • Added IntelligenceFeedFilters interface for filtering intelligence feed data.
    • Implemented fetchIntelligenceFeed function to retrieve intelligence signals for the trend radar.
  • web/src/lib/dashboard-brief.test.ts
    • Added unit tests for the buildDashboardDailyBrief utility function.
  • web/src/lib/dashboard-brief.ts
    • Introduced new types (DailyReport, DashboardDailyBriefHighlight, DashboardDailyBrief) for structuring daily brief data.
    • Implemented utility functions (buildDashboardDailyBrief, fetchLatestDashboardDailyBrief) to process daily reports into a dashboard-friendly format.
  • web/src/lib/dashboard-intelligence.test.ts
    • Added unit tests for the buildDashboardIntelligenceCards utility function.
  • web/src/lib/dashboard-intelligence.ts
    • Introduced DashboardIntelligenceCard interface for displaying intelligence signals.
    • Implemented utility functions (buildMetricLabel, buildReasonChips, buildSignalHref, buildResearchHref, buildDashboardIntelligenceCards) to transform raw intelligence feed items into displayable cards.
  • web/src/lib/types.ts
    • Added new TypeScript interfaces (IntelligenceMatchedTrack, IntelligenceFeedItem, IntelligenceFeedResponse) to support intelligence feed data structures.
Activity
  • jerry609 initiated the redesign of the dashboard into a daily brief format.
  • jerry609 provided comprehensive validation steps, including ESLint, TypeScript compilation, unit tests for new dashboard utilities, and Playwright E2E tests for the new dashboard workflow.
  • jerry609 noted that the dashboard screenshot in the README was regenerated to reflect the new UI.
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.

@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 a significant redesign of the dashboard, transforming it into a "daily brief" view. The changes are extensive, including a new main dashboard page, a new view component, several new library files for fetching and processing data for the brief, and new e2e and unit tests. The code is generally well-structured. I've identified a couple of areas for improvement regarding code duplication and configuration robustness. My detailed comments are below.

Comment on lines +39 to +56
function formatRelativeTime(value?: string | null): string {
if (!value) return "刚刚"

const parsed = new Date(value)
if (Number.isNaN(parsed.getTime())) return value

const diffMs = Date.now() - parsed.getTime()
const diffMinutes = Math.max(1, Math.floor(diffMs / 60_000))
const diffHours = Math.floor(diffMinutes / 60)
const diffDays = Math.floor(diffHours / 24)

if (diffMinutes < 60) return `${diffMinutes} 分钟前`
if (diffHours < 24) return `${diffHours} 小时前`
if (diffDays === 1) return "昨天"
if (diffDays < 7) return `${diffDays} 天前`

return parsed.toLocaleDateString("zh-CN", { month: "numeric", day: "numeric" })
}

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 function formatRelativeTime is duplicated in web/src/components/dashboard/DashboardDailyBriefView.tsx. To improve maintainability and avoid potential inconsistencies, this function should be extracted to a shared utility file (e.g., src/lib/formatters.ts) and imported where needed.

Comment on lines +267 to +283
async function resolveReportDirectory(): Promise<string | null> {
const candidates = [
path.join(process.cwd(), "reports", "dailypaper"),
path.join(process.cwd(), "..", "reports", "dailypaper"),
]

for (const candidate of candidates) {
try {
const stats = await fs.stat(candidate)
if (stats.isDirectory()) return candidate
} catch {
continue
}
}

return null
}

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 resolveReportDirectory function uses hardcoded relative paths to find the reports directory, which can be fragile and dependent on the current working directory. This might work for the current development and build setup, but it could easily break in different environments or if the project structure changes.

To make this more robust, consider supporting an environment variable (e.g., DAILY_REPORTS_PATH) to explicitly configure the reports directory path, while keeping the existing paths as fallbacks.

Suggested change
async function resolveReportDirectory(): Promise<string | null> {
const candidates = [
path.join(process.cwd(), "reports", "dailypaper"),
path.join(process.cwd(), "..", "reports", "dailypaper"),
]
for (const candidate of candidates) {
try {
const stats = await fs.stat(candidate)
if (stats.isDirectory()) return candidate
} catch {
continue
}
}
return null
}
async function resolveReportDirectory(): Promise<string | null> {
const candidates = [
process.env.DAILY_REPORTS_PATH,
path.join(process.cwd(), "reports", "dailypaper"),
path.join(process.cwd(), "..", "reports", "dailypaper"),
].filter((p): p is string => Boolean(p));
for (const candidate of candidates) {
try {
const stats = await fs.stat(candidate)
if (stats.isDirectory()) return candidate
} catch {
continue
}
}
return null
}

Copilot AI 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.

Pull request overview

Redesigns the /dashboard experience into a “daily brief” homepage that summarizes hot papers, community trend signals, and a decision-oriented action rail, while restoring key navigation and updating docs.

Changes:

  • Replaces the existing dashboard page with a new daily-brief server-rendered view powered by daily report + track feed + intelligence feed data.
  • Adds new dashboard “intelligence” + “daily brief” mapping utilities with unit tests and an E2E smoke test.
  • Restores /workflows in the sidebar and updates layout hydration handling + README screenshot blurb.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
web/src/lib/types.ts Adds intelligence feed response/item types used by the new dashboard.
web/src/lib/dashboard-intelligence.ts Maps intelligence feed items into display-ready dashboard trend cards.
web/src/lib/dashboard-intelligence.test.ts Unit tests for intelligence card mapping behavior.
web/src/lib/dashboard-brief.ts Builds dashboard brief model from a daily report; adds filesystem-based loader.
web/src/lib/dashboard-brief.test.ts Unit tests for daily brief transformation logic.
web/src/lib/dashboard-api.ts Adds fetchIntelligenceFeed client helper and filter typing.
web/src/components/layout/Sidebar.tsx Restores the /workflows navigation entry.
web/src/components/dashboard/DashboardDailyBriefView.tsx New daily brief UI: hot picks, trend radar, decision rail.
web/src/app/layout.tsx Adjusts hydration warning suppression and body classes.
web/src/app/dashboard/page.tsx Replaces old dashboard composition with daily-brief data orchestration + view.
web/e2e/dashboard-workflow.spec.ts Adds Playwright E2E check for the new dashboard layout and workflows link.
README.md Updates dashboard description to match the daily brief redesign.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread web/src/lib/types.ts
Comment on lines +176 to +178
export interface IntelligenceFeedItem {
id: string
source: "reddit" | "github" | "huggingface" | "twitter_x" | string

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

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

source is typed as "reddit" | "github" | ... | string, but the | string makes the whole union collapse to just string, so the literal variants provide no type safety. Consider either using source: string (and optionally a separate KnownIntelligenceSource union for narrowing) or removing | string and extending the union as needed.

Suggested change
export interface IntelligenceFeedItem {
id: string
source: "reddit" | "github" | "huggingface" | "twitter_x" | string
export type KnownIntelligenceSource = "reddit" | "github" | "huggingface" | "twitter_x"
export interface IntelligenceFeedItem {
id: string
source: string

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +56
function buildSignalHref(item: IntelligenceFeedItem): string {
const url = String(item.url || "").trim()
if (url) {
return url
}

const repo = String(item.repo_full_name || "").trim()
if (repo) {
return `https://github.com/${repo}`

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

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

buildSignalHref returns item.url as-is. Since this value comes from an API, it can contain unsafe schemes (e.g. javascript:) or malformed URLs; those would become clickable hrefs in the dashboard. Prefer using the existing safeHref helper (web/src/lib/utils.ts) and only allowing http(s) or relative /... links; otherwise fall back to a safe internal route.

Copilot uses AI. Check for mistakes.
Comment thread web/src/app/layout.tsx
Comment on lines 1 to 3
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { LayoutShell } from "@/components/layout/LayoutShell";

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

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

This file uses semicolons and a different formatting style than the rest of the web/src codebase (e.g. web/src/app/page.tsx, web/src/lib/dashboard-api.ts). To keep formatting consistent (and avoid formatter churn), please run the repo formatter or align the style in this file with the existing convention.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +272
async function resolveReportDirectory(): Promise<string | null> {
const candidates = [
path.join(process.cwd(), "reports", "dailypaper"),
path.join(process.cwd(), "..", "reports", "dailypaper"),
]

Copilot AI Mar 12, 2026

Copy link

Choose a reason for hiding this comment

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

fetchLatestDashboardDailyBrief reads the latest report from a local reports/dailypaper folder at request time. This is brittle in many Next.js deployments (serverless/edge/container images without generated reports) and adds per-request filesystem I/O + directory scanning. If this is intended to work in production, consider fetching the daily brief via an API endpoint or making the report path explicit via config, and caching the parsed result (e.g., in-memory with a short TTL) to avoid repeated scans.

Copilot uses AI. Check for mistakes.

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
web/src/components/dashboard/DashboardDailyBriefView.tsx (1)

249-258: Use <a> tag for external links for consistency.

HotPaperCard correctly uses a plain <a> tag for external URLs, but SignalCard uses Next.js Link with target="_blank". While this works, using <a> for external links is more semantically correct and maintains consistency across the codebase.

♻️ Proposed fix
         {item.isExternal ? (
-          <Link
+          <a
             href={item.href}
             target="_blank"
             rel="noreferrer"
             className="text-xs font-medium text-slate-500"
           >
             来源
-          </Link>
+          </a>
         ) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/components/dashboard/DashboardDailyBriefView.tsx` around lines 249 -
258, Replace the Next.js Link used for external items in DashboardDailyBriefView
(the JSX block that checks item.isExternal) with a plain anchor tag like
HotPaperCard does: render <a> with href={item.href}, target="_blank",
rel="noreferrer" and keep the same className and inner text ("来源"); update the
SignalCard/external-link usage in this component so all external URLs use <a>
for semantic consistency.
web/src/app/dashboard/page.tsx (1)

39-56: Consider extracting formatRelativeTime to a shared utility.

This function is duplicated identically in DashboardDailyBriefView.tsx (lines 88-105). Consider extracting it to a shared utility file (e.g., web/src/lib/format-utils.ts) to avoid duplication.

♻️ Proposed refactor

Create a new shared utility:

// web/src/lib/format-utils.ts
export function formatRelativeTime(value?: string | null): string {
  if (!value) return "刚刚"

  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return value

  const diffMs = Date.now() - parsed.getTime()
  const diffMinutes = Math.max(1, Math.floor(diffMs / 60_000))
  const diffHours = Math.floor(diffMinutes / 60)
  const diffDays = Math.floor(diffHours / 24)

  if (diffMinutes < 60) return `${diffMinutes} 分钟前`
  if (diffHours < 24) return `${diffHours} 小时前`
  if (diffDays === 1) return "昨天"
  if (diffDays < 7) return `${diffDays} 天前`

  return parsed.toLocaleDateString("zh-CN", { month: "numeric", day: "numeric" })
}

Then import in both files:

+import { formatRelativeTime } from "@/lib/format-utils"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/app/dashboard/page.tsx` around lines 39 - 56, The function
formatRelativeTime is duplicated; extract it into a single exported utility
(e.g., export function formatRelativeTime(...)) and replace the duplicates by
importing and calling that exported function; update the DashboardDailyBriefView
and the page component to remove their local formatRelativeTime definitions and
import the shared utility, keeping the exact function signature and behavior so
callers (e.g., places that call formatRelativeTime) remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/e2e/dashboard-workflow.spec.ts`:
- Around line 3-14: Add a visibility assertion for the restored sidebar nav
entry so the spec protects the Sidebar change: inside the "Dashboard Daily
Brief" test in web/e2e/dashboard-workflow.spec.ts (the test that visits
/dashboard and clicks the hero CTA), add an assertion that the sidebar link
labeled "Workflows" (from web/src/components/layout/Sidebar.tsx) is visible
using page.getByRole("link", { name: "Workflows" }) before clicking the hero CTA
or navigating away.

In `@web/src/lib/dashboard-api.ts`:
- Around line 115-140: fetchIntelligenceFeed is calling a non-existent backend
endpoint (/intelligence/feed) and silently returns an empty payload via
fetchJsonOrNull, so implement the missing backend or stop calling it: either add
a new router registered under the '/intelligence' prefix with a handler that
returns the IntelligenceFeedResponse shape (items, refreshed_at,
refresh_scheduled, keywords, watch_repos, subreddits) and wire it into the
server bootstrap, or change fetchIntelligenceFeed to use an existing backend
route (or surface errors instead of falling back to an empty state) so the UI
doesn't always show "no community signals".

In `@web/src/lib/dashboard-brief.ts`:
- Around line 35-39: The DailyReportQuery type and the queryPulse builder drop
the report's total_hits and instead derive hits from top_items.length,
underreporting queries; update the DailyReportQuery type to include total_hits?:
number, then change the logic in the queryPulse construction (the code that
currently uses top_items.length to set hits) to prefer report.total_hits when
present and fall back to top_items?.length otherwise, and add a regression test
that constructs a DailyReportQuery with total_hits > top_items.length to assert
queryPulse.hits equals total_hits; reference DailyReportQuery, total_hits,
top_items, and the queryPulse building function in your changes.

---

Nitpick comments:
In `@web/src/app/dashboard/page.tsx`:
- Around line 39-56: The function formatRelativeTime is duplicated; extract it
into a single exported utility (e.g., export function formatRelativeTime(...))
and replace the duplicates by importing and calling that exported function;
update the DashboardDailyBriefView and the page component to remove their local
formatRelativeTime definitions and import the shared utility, keeping the exact
function signature and behavior so callers (e.g., places that call
formatRelativeTime) remain unchanged.

In `@web/src/components/dashboard/DashboardDailyBriefView.tsx`:
- Around line 249-258: Replace the Next.js Link used for external items in
DashboardDailyBriefView (the JSX block that checks item.isExternal) with a plain
anchor tag like HotPaperCard does: render <a> with href={item.href},
target="_blank", rel="noreferrer" and keep the same className and inner text
("来源"); update the SignalCard/external-link usage in this component so all
external URLs use <a> for semantic consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9d1e5bb6-c247-4b01-805b-a0dc73ccfa66

📥 Commits

Reviewing files that changed from the base of the PR and between 6ca2707 and 23f2e4f.

⛔ Files ignored due to path filters (1)
  • asset/ui/dashboard.png is excluded by !**/*.png
📒 Files selected for processing (12)
  • README.md
  • web/e2e/dashboard-workflow.spec.ts
  • web/src/app/dashboard/page.tsx
  • web/src/app/layout.tsx
  • web/src/components/dashboard/DashboardDailyBriefView.tsx
  • web/src/components/layout/Sidebar.tsx
  • web/src/lib/dashboard-api.ts
  • web/src/lib/dashboard-brief.test.ts
  • web/src/lib/dashboard-brief.ts
  • web/src/lib/dashboard-intelligence.test.ts
  • web/src/lib/dashboard-intelligence.ts
  • web/src/lib/types.ts

Comment on lines +3 to +14
test.describe("Dashboard Daily Brief", () => {
test("surfaces the daily brief homepage and links into the full workbench", async ({ page }) => {
await page.goto("/dashboard")

await expect(page.getByText("Daily Research Brief", { exact: true })).toBeVisible()
await expect(page.getByRole("heading", { name: "今日热点推送" })).toBeVisible()
await expect(page.getByRole("heading", { name: "趋势雷达" })).toBeVisible()
await expect(page.getByRole("heading", { name: "今天先处理什么" })).toBeVisible()

await page.getByRole("link", { name: "打开完整工作台" }).first().click()
await expect(page).toHaveURL(/\/workflows/)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cover the restored sidebar entry too.

This spec only exercises the hero CTA. If the Workflows item disappears from web/src/components/layout/Sidebar.tsx again, the test still passes. Please add at least a visibility assertion for the sidebar link so the restored nav entry is actually protected.

✅ Minimal coverage addition
     await expect(page.getByRole("heading", { name: "今天先处理什么" })).toBeVisible()
+    await expect(page.getByRole("link", { name: "Workflows" })).toBeVisible()

     await page.getByRole("link", { name: "打开完整工作台" }).first().click()

As per coding guidelines, "If behavior changes, add or update tests".

📝 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
test.describe("Dashboard Daily Brief", () => {
test("surfaces the daily brief homepage and links into the full workbench", async ({ page }) => {
await page.goto("/dashboard")
await expect(page.getByText("Daily Research Brief", { exact: true })).toBeVisible()
await expect(page.getByRole("heading", { name: "今日热点推送" })).toBeVisible()
await expect(page.getByRole("heading", { name: "趋势雷达" })).toBeVisible()
await expect(page.getByRole("heading", { name: "今天先处理什么" })).toBeVisible()
await page.getByRole("link", { name: "打开完整工作台" }).first().click()
await expect(page).toHaveURL(/\/workflows/)
})
test.describe("Dashboard Daily Brief", () => {
test("surfaces the daily brief homepage and links into the full workbench", async ({ page }) => {
await page.goto("/dashboard")
await expect(page.getByText("Daily Research Brief", { exact: true })).toBeVisible()
await expect(page.getByRole("heading", { name: "今日热点推送" })).toBeVisible()
await expect(page.getByRole("heading", { name: "趋势雷达" })).toBeVisible()
await expect(page.getByRole("heading", { name: "今天先处理什么" })).toBeVisible()
await expect(page.getByRole("link", { name: "Workflows" })).toBeVisible()
await page.getByRole("link", { name: "打开完整工作台" }).first().click()
await expect(page).toHaveURL(/\/workflows/)
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/e2e/dashboard-workflow.spec.ts` around lines 3 - 14, Add a visibility
assertion for the restored sidebar nav entry so the spec protects the Sidebar
change: inside the "Dashboard Daily Brief" test in
web/e2e/dashboard-workflow.spec.ts (the test that visits /dashboard and clicks
the hero CTA), add an assertion that the sidebar link labeled "Workflows" (from
web/src/components/layout/Sidebar.tsx) is visible using page.getByRole("link", {
name: "Workflows" }) before clicking the hero CTA or navigating away.

Comment on lines +115 to +140
export async function fetchIntelligenceFeed(
userId: string = "default",
limit: number = 6,
filters?: IntelligenceFeedFilters,
): Promise<IntelligenceFeedResponse> {
const qs = new URLSearchParams({
user_id: userId,
limit: String(limit),
})
if (filters?.source) qs.set("source", filters.source)
if (filters?.keyword) qs.set("keyword", filters.keyword)
if (filters?.repo) qs.set("repo", filters.repo)
if (filters?.sortBy) qs.set("sort_by", filters.sortBy)
if (filters?.sortOrder) qs.set("sort_order", filters.sortOrder)
if (filters?.trackId) qs.set("track_id", String(filters.trackId))

const payload = await fetchJsonOrNull<IntelligenceFeedResponse>(`/intelligence/feed?${qs.toString()}`)
return payload || {
items: [],
refreshed_at: null,
refresh_scheduled: false,
keywords: [],
watch_repos: [],
subreddits: [],
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 --type=py 'prefix\s*=\s*["'"'"']/intelligence["'"'"']|@router\.(get|post)\(\s*["'"'"']/feed["'"'"']|/intelligence/feed' src || true

Repository: jerry609/PaperBot

Length of output: 43


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search more broadly for intelligence endpoints
echo "=== Searching for 'intelligence' in backend files ==="
rg -i 'intelligence' src --type=py -l || echo "No matches for 'intelligence'"

echo ""
echo "=== Searching for 'feed' endpoint patterns ==="
rg '@.*route.*feed|def.*feed|/feed' src --type=py -C2 || echo "No matches for 'feed' patterns"

echo ""
echo "=== Checking backend structure ==="
find src -type f -name "*.py" | head -20 || echo "No Python files found in src"

echo ""
echo "=== Looking at main app/router files ==="
find src -type f \( -name "main.py" -o -name "app.py" -o -name "*router*" -o -name "*api*" \) | head -20

Repository: jerry609/PaperBot

Length of output: 12049


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Main API file ==="
cat -n src/paperbot/api/main.py | head -100

echo ""
echo "=== Search for 'include_router' or router registration ==="
rg 'include_router|@app\.|APIRouter' src/paperbot/api/main.py -A 2

echo ""
echo "=== Verify: no /intelligence prefix in any route ==="
rg '/intelligence' src --type=py || echo "No routes with /intelligence prefix found"

Repository: jerry609/PaperBot

Length of output: 5759


Land the /intelligence/feed backend endpoint in the same PR.

This helper silently converts fetch failures into empty-state payloads. The /intelligence/feed endpoint does not exist in the backend—no routers are registered with an /intelligence prefix, and the endpoint is absent from all route files. Without the backend implementation, this new intelligence section will always display "no community signals" instead of fetching real data. Either implement the backend route or gate this call behind an existing endpoint.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/dashboard-api.ts` around lines 115 - 140, fetchIntelligenceFeed
is calling a non-existent backend endpoint (/intelligence/feed) and silently
returns an empty payload via fetchJsonOrNull, so implement the missing backend
or stop calling it: either add a new router registered under the '/intelligence'
prefix with a handler that returns the IntelligenceFeedResponse shape (items,
refreshed_at, refresh_scheduled, keywords, watch_repos, subreddits) and wire it
into the server bootstrap, or change fetchIntelligenceFeed to use an existing
backend route (or surface errors instead of falling back to an empty state) so
the UI doesn't always show "no community signals".

Comment on lines +35 to +39
type DailyReportQuery = {
normalized_query?: string
raw_query?: string
top_items?: DailyReportItem[]
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use the report’s total_hits for query pulse.

The upstream DailyPaper report already carries per-query total_hits, but this model drops that field and then computes hits from top_items.length. Because top_items is just the retained shortlist, a query with 40 hits and 4 saved items is rendered as hits: 4, which underreports activity and can mis-rank the trend radar. Please carry total_hits through and prefer it when building queryPulse; add a regression test where total_hits > top_items.length.

🛠️ Proposed fix
 type DailyReportQuery = {
   normalized_query?: string
   raw_query?: string
+  total_hits?: number
   top_items?: DailyReportItem[]
 }
@@
   const queryRows = (report.queries || []).map((query) => ({
     queryLabel: String(query.normalized_query || query.raw_query || "").trim(),
+    totalHits: Number(query.total_hits ?? query.top_items?.length ?? 0),
     items: query.top_items || [],
   }))
@@
   const queryPulse = queryRows
     .filter((row) => row.queryLabel)
     .map((row) => ({
       query: row.queryLabel,
-      hits: row.items.length,
+      hits: row.totalHits,
     }))

Also applies to: 195-232

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/lib/dashboard-brief.ts` around lines 35 - 39, The DailyReportQuery
type and the queryPulse builder drop the report's total_hits and instead derive
hits from top_items.length, underreporting queries; update the DailyReportQuery
type to include total_hits?: number, then change the logic in the queryPulse
construction (the code that currently uses top_items.length to set hits) to
prefer report.total_hits when present and fall back to top_items?.length
otherwise, and add a regression test that constructs a DailyReportQuery with
total_hits > top_items.length to assert queryPulse.hits equals total_hits;
reference DailyReportQuery, total_hits, top_items, and the queryPulse building
function in your changes.

@jerry609
jerry609 merged commit 4a4928a into master Mar 12, 2026
14 checks passed
@jerry609
jerry609 deleted the pr/dashboard-daily-brief branch March 12, 2026 05:50
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