perf(kanban): stop board cards re-rendering on every agent event
Memoize the board so streaming events from an active run no longer force all 38 collapsed cards to re-render — only the open CardModal/AgentRunBar re-renders, since its props stay referentially equal. - KanbanCard: wrap in React.memo; take isRunning/bevyRunning primitives instead of the whole orchestrator object; onOpen now takes the card id. - useOrchestrator: expose a memoized activeByCard map, recomputed only when runs/bevyRunning change (not on every streamed text/tool event). - KanbanBoardPage: pass a stable openById callback + primitive props so memo bails; memoize StatCard; extract the static category legend to a memoized CategoryLegend component. Also drops the per-card .filter().find() status scans each render.
This commit is contained in:
@@ -53,6 +53,12 @@ export interface UseOrchestrator {
|
||||
bevyIsRunning: (runId: string) => boolean;
|
||||
/** Re-fetch a run's Bevy status from the server (truth after a reconnect). */
|
||||
refreshBevyStatus: (runId: string) => Promise<void>;
|
||||
/**
|
||||
* Active runs indexed by card id. Memoized and referentially stable unless
|
||||
* the active set actually changes (not on every streamed event), so memoized
|
||||
* card components can read their flags without re-rendering on noise.
|
||||
*/
|
||||
activeByCard: Map<string, { running: boolean; bevy: boolean; runId: string }>;
|
||||
}
|
||||
|
||||
export function useOrchestrator(): UseOrchestrator {
|
||||
@@ -283,6 +289,21 @@ export function useOrchestrator(): UseOrchestrator {
|
||||
|
||||
const bevyIsRunning = useCallback((runId: string) => bevyRunning.has(runId), [bevyRunning]);
|
||||
|
||||
/**
|
||||
* Card-id index of active runs. Recomputed only when `runs` or `bevyRunning`
|
||||
* changes — NOT on every streamed event — so memoized consumers stay stable.
|
||||
* Replaces the per-card `.filter().find()` scans the board used to do.
|
||||
*/
|
||||
const activeByCard = useMemo(() => {
|
||||
const m = new Map<string, { running: boolean; bevy: boolean; runId: string }>();
|
||||
for (const r of runs) {
|
||||
if (r.status === 'running') {
|
||||
m.set(r.cardId, { running: true, bevy: bevyRunning.has(r.id), runId: r.id });
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}, [runs, bevyRunning]);
|
||||
|
||||
const refreshBevyStatus = useCallback(async (runId: string) => {
|
||||
try {
|
||||
const { running } = await orchestratorApi.bevyStatus(runId);
|
||||
@@ -317,5 +338,6 @@ export function useOrchestrator(): UseOrchestrator {
|
||||
stopBevy,
|
||||
bevyIsRunning,
|
||||
refreshBevyStatus,
|
||||
activeByCard,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user