From 52fc10f0eb4567d49d1f348e3443d3315983a301 Mon Sep 17 00:00:00 2001 From: Prateek Date: Sat, 14 Feb 2026 20:24:33 +0530 Subject: [PATCH] fix: address remaining bugbot review issues from PR #26 - SSE: separate service errors from enqueue errors so transient failures skip the poll instead of permanently killing the stream - SSE: send single snapshot event per poll instead of N per-session activity events (matches SSESnapshotEvent type, constant traffic) - Stats: only count reviewDecision === "pending" as needing review, not "none" (which is the unenriched default) Co-Authored-By: Claude Sonnet 4.5 --- packages/web/src/app/api/events/route.ts | 26 +++++++++++++++--------- packages/web/src/lib/serialize.ts | 5 +---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/web/src/app/api/events/route.ts b/packages/web/src/app/api/events/route.ts index 5606ed4b2..2d37eeab8 100644 --- a/packages/web/src/app/api/events/route.ts +++ b/packages/web/src/app/api/events/route.ts @@ -56,22 +56,28 @@ export async function GET(): Promise { // Poll for session state changes every 5 seconds updates = setInterval(() => { void (async () => { + let dashboardSessions; try { const { sessionManager } = await getServices(); const sessions = await sessionManager.list(); - const dashboardSessions = sessions.map(sessionToDashboard); + dashboardSessions = sessions.map(sessionToDashboard); + } catch { + // Transient service error — skip this poll, retry on next interval + return; + } - for (const s of dashboardSessions) { - const event = { - type: "session.activity", - sessionId: s.id, - activity: s.activity, + try { + const event = { + type: "snapshot", + sessions: dashboardSessions.map((s) => ({ + id: s.id, status: s.status, + activity: s.activity, attentionLevel: getAttentionLevel(s), - timestamp: new Date().toISOString(), - }; - controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)); - } + lastActivityAt: s.lastActivityAt, + })), + }; + controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`)); } catch { // enqueue failure means the stream is closed — clean up both intervals clearInterval(updates); diff --git a/packages/web/src/lib/serialize.ts b/packages/web/src/lib/serialize.ts index 20e29a4ee..aaedef549 100644 --- a/packages/web/src/lib/serialize.ts +++ b/packages/web/src/lib/serialize.ts @@ -123,10 +123,7 @@ export function computeStats(sessions: DashboardSession[]): DashboardStats { workingSessions: sessions.filter((s) => s.activity === "active").length, openPRs: sessions.filter((s) => s.pr?.state === "open").length, needsReview: sessions.filter( - (s) => - s.pr && - !s.pr.isDraft && - (s.pr.reviewDecision === "pending" || s.pr.reviewDecision === "none"), + (s) => s.pr && !s.pr.isDraft && s.pr.reviewDecision === "pending", ).length, }; }