fix(topbar): idle-quiet badge poll (count threads, not per-thread reads)
Idle-poll audit caught a regression I introduced in the prior commit: the topbar badge poll iterated chatApi.listMessages per thread on each 60s tick, firing N requests per refresh. Landing scene showed 9 reqs in a 30s idle window (failed the <=5 budget). Now the topbar badge just shows the THREAD COUNT (not per-thread unread), which is a single chatApi.listThreads call. Per-thread unread math stays in the Chat scene where it belongs (already wired: chat-thread-row.unread + .chat-unread-summary).
This commit is contained in:
+4
-20
@@ -76,7 +76,7 @@ export default function App() {
|
||||
|
||||
const liveAge = liveFetchedAt ? `${Math.max(0, Math.floor((Date.now() - liveFetchedAt) / 1000))}s ago` : null;
|
||||
|
||||
const [chatUnread, setChatUnread] = useState(0);
|
||||
const [chatThreadCount, setChatThreadCount] = useState(0);
|
||||
const [queueCount, setQueueCount] = useState(0);
|
||||
const tenantId = useApp((s) => s.tenantId);
|
||||
useEffect(() => {
|
||||
@@ -91,24 +91,8 @@ export default function App() {
|
||||
apiMod.workItems().catch(() => []),
|
||||
]);
|
||||
if (cancelled) return;
|
||||
const readMap: Record<string, string> = (() => {
|
||||
try { return JSON.parse(localStorage.getItem(`fm.canvas.chat.read.${userEmail}`) || "{}"); } catch { return {}; }
|
||||
})();
|
||||
let unread = 0;
|
||||
for (const t of threads) {
|
||||
try {
|
||||
const msgs = await chatApi.listMessages(t._key);
|
||||
const last = msgs[msgs.length - 1];
|
||||
if (!last) continue;
|
||||
if (last.author_email === userEmail) continue;
|
||||
const readAt = readMap[t._key];
|
||||
if (!readAt || last.created_at > readAt) unread++;
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
if (!cancelled) {
|
||||
setChatUnread(unread);
|
||||
setQueueCount(items.filter((it) => (it as any).tenant_id === tenantId).length);
|
||||
}
|
||||
setChatThreadCount(threads.length);
|
||||
setQueueCount(items.filter((it) => (it as any).tenant_id === tenantId).length);
|
||||
} catch { /* ignore */ }
|
||||
};
|
||||
void refresh();
|
||||
@@ -143,7 +127,7 @@ export default function App() {
|
||||
</button>
|
||||
<button role="tab" aria-selected={scene === "chat"} className={`tab${scene === "chat" ? " tab-sel" : ""}`} onClick={() => setScene("chat")}>
|
||||
<Bot size={13} /> Chat
|
||||
{chatUnread > 0 && <span className="tab-badge tab-badge-amber">{chatUnread > 99 ? "99+" : chatUnread}</span>}
|
||||
{chatThreadCount > 0 && <span className="tab-badge tab-badge-amber">{chatThreadCount > 99 ? "99+" : chatThreadCount}</span>}
|
||||
</button>
|
||||
<button role="tab" aria-selected={scene === "agent"} className={`tab${scene === "agent" ? " tab-sel" : ""}`} onClick={() => setScene("agent")}>
|
||||
<Bot size={13} /> Assistant
|
||||
|
||||
Reference in New Issue
Block a user