-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Summary
When running large teams (10+ agents, 100+ tasks), the dashboard receives hundreds of WebSocket messages per minute. Without throttling and virtualization, the React tree re-renders on every message, causing jank and eventually freezing the browser tab.
This issue proposes WebSocket message batching on the server and virtualized rendering on the client.
Problem Demonstration
With a 10-agent team running fast tasks:
- 200+ WebSocket messages/minute
- Each message triggers a full
allInboxesstate update inApp.jsx - React re-renders the entire component tree (TeamCard Γ 10, AgentCard Γ 50, etc.)
- Chrome DevTools shows: ~300ms scripting time per second β visible lag
Solution 1: Server-side message batching (server.js)
Instead of broadcasting every individual file change, batch updates and send a diff every 500ms:
// Collect changes in a buffer
let pendingBroadcast = null;
let broadcastTimer = null;
function scheduleBroadcast(type, data) {
if (!pendingBroadcast) pendingBroadcast = {};
pendingBroadcast[type] = data; // Merge/overwrite β only latest state matters
if (!broadcastTimer) {
broadcastTimer = setTimeout(() => {
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(pendingBroadcast));
}
});
pendingBroadcast = null;
broadcastTimer = null;
}, 500); // Max 2 broadcasts/second
}
}Solution 2: Client-side virtualized message list
Replace the RealTimeMessages overflow div with @tanstack/react-virtual:
npm install @tanstack/react-virtualimport { useVirtualizer } from '@tanstack/react-virtual';
export function RealTimeMessages({ allInboxes }) {
const parentRef = useRef(null);
const allMessages = useMemo(() => flattenInboxes(allInboxes), [allInboxes]);
const virtualizer = useVirtualizer({
count: allMessages.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80, // estimated row height px
overscan: 10,
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() + 'px', position: 'relative' }}>
{virtualizer.getVirtualItems().map(vRow => (
<div key={vRow.index} style={{ position: 'absolute', top: vRow.start + 'px', width: '100%' }}>
<MessageRow msg={allMessages[vRow.index]} />
</div>
))}
</div>
</div>
);
}Solution 3: useMemo + React.memo audit
flattenInboxes()inRealTimeMessagesshould be memoized (already done β )TaskListrows should be wrapped inReact.memoTeamCardshouldReact.memowith shallow compare onteamprop
Benchmarks (Target)
| Scenario | Before | After (target) |
|---|---|---|
| 10 agents, 100 msg/min | ~300ms/s scripting | < 20ms/s |
| 1000 messages in list | Renders all 1000 DOM nodes | Renders ~10 visible nodes |
| WebSocket update rate | Every event | Batched 500ms |
Acceptance Criteria
- Server batches WebSocket broadcasts to max 2/second under load
-
RealTimeMessagesuses virtual list for > 50 messages -
TeamCardandAgentCardwrapped inReact.memo - Chrome DevTools Profiler shows < 50ms scripting time during active 10-agent run
- No visible UI jank during rapid message bursts
- Virtualized list maintains scroll position correctly on new messages (auto-scroll-to-bottom)
- Existing Vitest tests still pass
References
- TanStack Virtual: https://tanstack.com/virtual/latest
- React memo and performance: https://react.dev/reference/react/memo
- WebSocket batching pattern: Server-Sent Events vs WebSocket comparison
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request