Limit the dashboard approval banner to real mailbox approval requests instead of task plan-approval states. - Stop awaiting-approval task updates from fabricating Open Mailbox banner candidates or refreshing mailbox counts. - Gate DashboardBanners rendering to approval:<id> candidates while preserving pending-count race protection. - Update dashboard tests, documentation, and release notes for mailbox-only approval banners. Files changed: .changeset/fn-7096-approval-banner-mailbox.md | 7 + docs/dashboard-guide.md | 2 +- packages/dashboard/app/App.tsx | 3 +- .../app/components/__tests__/App.test.tsx | 33 ++-- .../app/components/dashboard/DashboardBanners.tsx | 9 +- .../dashboard/__tests__/DashboardBanners.test.tsx | 65 +++++++- .../hooks/__tests__/sseSplitIntegration.test.ts | 28 ++-- .../app/hooks/__tests__/useApprovalBanner.test.ts | 177 ++++++++------------- packages/dashboard/app/hooks/useApprovalBanner.ts | 34 +--- packages/dashboard/app/hooks/useMailboxUnread.ts | 5 +- 10 files changed, 185 insertions(+), 178 deletions(-) Fusion-Task-Id: FN-7096 Fusion-Task-Lineage: 40885aba-6a9f-4720-bea6-5a6f155c326d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
/*
|
|
FNXC:MailboxBadge 2026-06-24-00:00:
|
|
Header/mobile-nav unread + pending-approval counts for the mailbox, refreshed on message and approval SSE events. Extracted from AppInner; exposes `refresh` for reconnect/SSE-driven count refresh and `setMailboxUnreadCount` because MailboxView reports its own count changes through onUnreadCountChange.
|
|
|
|
FNXC:MailboxBadge 2026-06-26-00:00:
|
|
Pending approval counts are mailbox-only and refresh from approval:* events backed by ApprovalRequest rows. Task awaiting-approval transitions must not refresh or inflate these mailbox counts.
|
|
*/
|
|
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import { fetchUnreadCount } from "../api";
|
|
import { subscribeSse } from "../sse-bus";
|
|
|
|
export interface UseMailboxUnreadResult {
|
|
mailboxUnreadCount: number;
|
|
mailboxPendingApprovalCount: number;
|
|
setMailboxUnreadCount: (count: number) => void;
|
|
refresh: () => void;
|
|
}
|
|
|
|
export function useMailboxUnread(currentProjectId: string | undefined): UseMailboxUnreadResult {
|
|
const [mailboxUnreadCount, setMailboxUnreadCount] = useState(0);
|
|
const [mailboxPendingApprovalCount, setMailboxPendingApprovalCount] = useState(0);
|
|
|
|
const refresh = useCallback(() => {
|
|
fetchUnreadCount(currentProjectId)
|
|
.then((data: { unreadCount: number; pendingApprovalCount?: number }) => {
|
|
setMailboxUnreadCount(data.unreadCount);
|
|
setMailboxPendingApprovalCount(data.pendingApprovalCount ?? 0);
|
|
})
|
|
.catch((err) => {
|
|
console.warn("[App] Failed to fetch mailbox unread count:", err);
|
|
});
|
|
}, [currentProjectId]);
|
|
|
|
useEffect(() => {
|
|
refresh();
|
|
|
|
const params = new URLSearchParams();
|
|
if (currentProjectId) {
|
|
params.set("projectId", currentProjectId);
|
|
}
|
|
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
|
|
return subscribeSse(`/api/events${query}`, {
|
|
onReconnect: refresh,
|
|
events: {
|
|
"message:sent": refresh,
|
|
"message:received": refresh,
|
|
"message:read": refresh,
|
|
"message:deleted": refresh,
|
|
"approval:requested": refresh,
|
|
"approval:updated": refresh,
|
|
"approval:decided": refresh,
|
|
},
|
|
});
|
|
}, [currentProjectId, refresh]);
|
|
|
|
return { mailboxUnreadCount, mailboxPendingApprovalCount, setMailboxUnreadCount, refresh };
|
|
}
|