From b398e68c8b84c7117e855cf4935101756bcd8558 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Tue, 16 Jun 2026 21:35:09 +0330 Subject: [PATCH] Sidebar: pending-review count badge on the Review inbox item MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After dispatching an agent run, its proposal goes to the review inbox, but the board gave no signal — users thought "nothing happened". The Review inbox nav item now shows an amber count of held actions awaiting review (polled), so waiting work is always visible. Co-Authored-By: Claude Opus 4.8 --- client/src/components/AppShell.tsx | 38 ++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/client/src/components/AppShell.tsx b/client/src/components/AppShell.tsx index 0ebf3d8..a73dded 100644 --- a/client/src/components/AppShell.tsx +++ b/client/src/components/AppShell.tsx @@ -1,4 +1,4 @@ -import type { ReactNode } from 'react' +import { useEffect, useState, type ReactNode } from 'react' import { Link, useLocation } from 'react-router' import { BookMarked, @@ -21,12 +21,39 @@ import { } from 'lucide-react' import { Button } from '@/components/ui/button' import { Separator } from '@/components/ui/separator' +import { api } from '@/lib/api' import { cn } from '@/lib/utils' import { useAuth } from '@/store/auth' +/** Polls the count of held actions awaiting review, so the nav can flag work is waiting. */ +function usePendingReviewCount(organizationId: string | null): number { + const [count, setCount] = useState(0) + useEffect(() => { + if (!organizationId) return + let cancelled = false + const tick = async () => { + try { + const items = await api.get(`/api/governance/reviews?organizationId=${organizationId}&status=Pending`) + if (!cancelled) setCount(items.length) + } catch { + // leave the last known count on a transient failure + } + } + void tick() + const id = setInterval(tick, 12000) + return () => { + cancelled = true + clearInterval(id) + } + }, [organizationId]) + return count +} + export function AppShell({ children }: { children: ReactNode }) { const email = useAuth((s) => s.email) const logout = useAuth((s) => s.logout) + const organizationId = useAuth((s) => s.organizationId) + const reviewCount = usePendingReviewCount(organizationId) return (
@@ -59,7 +86,7 @@ export function AppShell({ children }: { children: ReactNode }) { - + @@ -115,12 +142,14 @@ function NavItem({ to, muted, color, + badge, }: { icon: LucideIcon label: string to?: string muted?: boolean color?: string + badge?: number }) { const location = useLocation() const active = to ? location.pathname === to : false @@ -142,6 +171,11 @@ function NavItem({ {label} + {!!badge && badge > 0 && ( + + {badge} + + )} {muted && soon} )