diff --git a/server/src/routes/sidebar-badges.ts b/server/src/routes/sidebar-badges.ts index 928f0181..5b689a86 100644 --- a/server/src/routes/sidebar-badges.ts +++ b/server/src/routes/sidebar-badges.ts @@ -5,15 +5,15 @@ import { joinRequests } from "@paperclipai/db"; import { sidebarBadgeService } from "../services/sidebar-badges.js"; import { issueService } from "../services/issues.js"; import { accessService } from "../services/access.js"; +import { dashboardService } from "../services/dashboard.js"; import { assertCompanyAccess } from "./authz.js"; -const INBOX_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked"] as const; - export function sidebarBadgeRoutes(db: Db) { const router = Router(); const svc = sidebarBadgeService(db); const issueSvc = issueService(db); const access = accessService(db); + const dashboard = dashboardService(db); router.get("/companies/:companyId/sidebar-badges", async (req, res) => { const companyId = req.params.companyId as string; @@ -36,19 +36,16 @@ export function sidebarBadgeRoutes(db: Db) { .then((rows) => Number(rows[0]?.count ?? 0)) : 0; - const unreadTouchedIssueCount = - req.actor.type === "board" && req.actor.userId - ? await issueSvc.countUnreadTouchedByUser( - companyId, - req.actor.userId, - INBOX_ISSUE_STATUSES.join(","), - ) - : 0; - const badges = await svc.get(companyId, { joinRequests: joinRequestCount, - unreadTouchedIssues: unreadTouchedIssueCount, }); + const summary = await dashboard.summary(companyId); + const staleIssueCount = await issueSvc.staleCount(companyId, 24 * 60); + const alertsCount = + (summary.agents.error > 0 ? 1 : 0) + + (summary.costs.monthBudgetCents > 0 && summary.costs.monthUtilizationPercent >= 80 ? 1 : 0); + badges.inbox = badges.failedRuns + alertsCount + staleIssueCount; + res.json(badges); }); diff --git a/ui/src/pages/Inbox.tsx b/ui/src/pages/Inbox.tsx index 6c5c86a8..71824adc 100644 --- a/ui/src/pages/Inbox.tsx +++ b/ui/src/pages/Inbox.tsx @@ -42,6 +42,7 @@ import { PageTabBar } from "../components/PageTabBar"; import type { HeartbeatRun, Issue, JoinRequest } from "@paperclipai/shared"; const STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours +const RECENT_ISSUES_LIMIT = 100; const FAILED_RUN_STATUSES = new Set(["failed", "timed_out"]); const ACTIONABLE_APPROVAL_STATUSES = new Set(["pending", "revision_requested"]); @@ -390,7 +391,7 @@ export function Inbox() { ); const touchedIssues = useMemo( - () => [...touchedIssuesRaw].sort(sortByMostRecentActivity), + () => [...touchedIssuesRaw].sort(sortByMostRecentActivity).slice(0, RECENT_ISSUES_LIMIT), [sortByMostRecentActivity, touchedIssuesRaw], ); @@ -504,12 +505,8 @@ export function Inbox() { const hasStale = staleIssues.length > 0; const hasJoinRequests = joinRequests.length > 0; const hasTouchedIssues = touchedIssues.length > 0; - const unreadTouchedCount = touchedIssues.filter((issue) => issue.isUnreadForMe).length; const newItemCount = - unreadTouchedCount + - joinRequests.length + - actionableApprovals.length + failedRuns.length + staleIssues.length + (showAggregateAgentError ? 1 : 0) + @@ -539,12 +536,12 @@ export function Inbox() { const showStaleSection = tab === "new" ? hasStale : showStaleCategory && hasStale; const visibleSections = [ - showTouchedSection ? "issues_i_touched" : null, - showApprovalsSection ? "approvals" : null, - showJoinRequestsSection ? "join_requests" : null, showFailedRunsSection ? "failed_runs" : null, showAlertsSection ? "alerts" : null, showStaleSection ? "stale_work" : null, + showApprovalsSection ? "approvals" : null, + showJoinRequestsSection ? "join_requests" : null, + showTouchedSection ? "issues_i_touched" : null, ].filter((key): key is SectionKey => key !== null); const allLoaded = @@ -592,7 +589,7 @@ export function Inbox() { All categories - Issues I touched + My recent issues Join requests Approvals Failed runs @@ -638,48 +635,6 @@ export function Inbox() { /> )} - {showTouchedSection && ( - <> - {showSeparatorBefore("issues_i_touched") && } -
-

- Issues I Touched -

-
- {touchedIssues.map((issue) => ( - - - - - - - - {issue.identifier ?? issue.id.slice(0, 8)} - - {issue.title} - - {issue.lastExternalCommentAt - ? `commented ${timeAgo(issue.lastExternalCommentAt)}` - : `updated ${timeAgo(issue.updatedAt)}`} - - - ))} -
-
- - )} - {showApprovalsSection && ( <> {showSeparatorBefore("approvals") && } @@ -895,6 +850,48 @@ export function Inbox() { )} + + {showTouchedSection && ( + <> + {showSeparatorBefore("issues_i_touched") && } +
+

+ My Recent Issues +

+
+ {touchedIssues.map((issue) => ( + + + + + + + + {issue.identifier ?? issue.id.slice(0, 8)} + + {issue.title} + + {issue.lastExternalCommentAt + ? `commented ${timeAgo(issue.lastExternalCommentAt)}` + : `updated ${timeAgo(issue.updatedAt)}`} + + + ))} +
+
+ + )} ); }