diff --git a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx index 8461787d..770b996f 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/app.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/app.tsx @@ -184,6 +184,22 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message handleUserMessage }), [handleCopyChat, handleUserMessage]); + // Memoized status bar change handler to prevent infinite update loop + const handleStatusBarChange = useCallback((status: any) => { + setStatusBar((prev: any) => { + // Shallow compare previous and next status + const next = { ...status, context: lockedContext }; + const keys = Object.keys(next); + if ( + prev && + keys.every(key => prev[key] === next[key]) + ) { + return prev; + } + return next; + }); + }, [lockedContext]); + return (
@@ -194,10 +210,7 @@ const App = forwardRef<{ handleCopyChat: () => void; handleUserMessage: (message loadingResponse={loadingResponse} workflow={workflowRef.current} dispatch={dispatch} - onStatusBarChange={status => setStatusBar({ - ...status, - context: lockedContext, - })} + onStatusBarChange={handleStatusBarChange} />
diff --git a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx index 68520471..04c22999 100644 --- a/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx +++ b/apps/rowboat/app/projects/[projectId]/copilot/components/messages.tsx @@ -413,9 +413,14 @@ function AssistantMessage({ const [panelOpen, setPanelOpen] = useState(false); // collapsed by default // At the end of the render, call onStatusBarChange with the current status bar props + // Track the latest status bar info + const latestStatusBar = useRef(null); + + // Only call onStatusBarChange if the serializable status actually changes + const lastStatusRef = useRef(null); useEffect(() => { if (onStatusBarChange) { - onStatusBarChange({ + const status = { allCardsLoaded, allApplied, appliedCount, @@ -423,10 +428,18 @@ function AssistantMessage({ streamingLine, completedSummary, hasPanelWarning, - handleApplyAll, - }); + // Exclude handleApplyAll from comparison + }; + if (!lastStatusRef.current || JSON.stringify(lastStatusRef.current) !== JSON.stringify(status)) { + lastStatusRef.current = status; + onStatusBarChange({ + ...status, + handleApplyAll, // pass the function, but don't compare it + }); + } } - }, [allCardsLoaded, allApplied, appliedCount, pendingCount, streamingLine, completedSummary, hasPanelWarning, handleApplyAll, onStatusBarChange]); + // Only depend on the serializable values, not the function + }, [allCardsLoaded, allApplied, appliedCount, pendingCount, streamingLine, completedSummary, hasPanelWarning, onStatusBarChange, handleApplyAll]); // Render all cards inline, not in a panel return (