feat: redesign dashboard daily brief#355
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThe 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello, 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
🧠 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
Activity
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| 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" }) | ||
| } |
There was a problem hiding this comment.
| 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 | ||
| } |
There was a problem hiding this comment.
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.
| 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 | |
| } |
There was a problem hiding this comment.
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
/workflowsin 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.
| export interface IntelligenceFeedItem { | ||
| id: string | ||
| source: "reddit" | "github" | "huggingface" | "twitter_x" | string |
There was a problem hiding this comment.
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.
| 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 |
| 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}` |
There was a problem hiding this comment.
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.
| import type { Metadata } from "next"; | ||
| import { Geist, Geist_Mono } from "next/font/google"; | ||
| import "./globals.css"; | ||
| import { LayoutShell } from "@/components/layout/LayoutShell"; |
There was a problem hiding this comment.
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.
| async function resolveReportDirectory(): Promise<string | null> { | ||
| const candidates = [ | ||
| path.join(process.cwd(), "reports", "dailypaper"), | ||
| path.join(process.cwd(), "..", "reports", "dailypaper"), | ||
| ] | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
web/src/components/dashboard/DashboardDailyBriefView.tsx (1)
249-258: Use<a>tag for external links for consistency.
HotPaperCardcorrectly uses a plain<a>tag for external URLs, butSignalCarduses Next.jsLinkwithtarget="_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 extractingformatRelativeTimeto 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
⛔ Files ignored due to path filters (1)
asset/ui/dashboard.pngis excluded by!**/*.png
📒 Files selected for processing (12)
README.mdweb/e2e/dashboard-workflow.spec.tsweb/src/app/dashboard/page.tsxweb/src/app/layout.tsxweb/src/components/dashboard/DashboardDailyBriefView.tsxweb/src/components/layout/Sidebar.tsxweb/src/lib/dashboard-api.tsweb/src/lib/dashboard-brief.test.tsweb/src/lib/dashboard-brief.tsweb/src/lib/dashboard-intelligence.test.tsweb/src/lib/dashboard-intelligence.tsweb/src/lib/types.ts
| 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/) | ||
| }) |
There was a problem hiding this comment.
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.
| 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.
| 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: [], | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 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 || trueRepository: 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 -20Repository: 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".
| type DailyReportQuery = { | ||
| normalized_query?: string | ||
| raw_query?: string | ||
| top_items?: DailyReportItem[] | ||
| } |
There was a problem hiding this comment.
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.
Summary
/dashboardinto a daily brief with hot picks, trend radar, and a decision rail/workflowssidebar entrymasterValidation
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.tsnpx tsc --noEmitnpm run test -- src/lib/dashboard-brief.test.ts src/lib/dashboard-intelligence.test.tsE2E_BASE_URL=http://127.0.0.1:3000 npx playwright test e2e/dashboard-workflow.spec.ts --project=chromiumNotes
Summary by CodeRabbit
New Features
Chores