From 2c493711026b65bffba96312443a91f5cdc684be Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 6 Feb 2026 13:11:49 -0600 Subject: [PATCH] fix(CQ-WEB-2): Fix missing dependency in FilterBar useEffect The debounced search useEffect accessed `filters` and `onFilterChange` without including them in the dependency array. Fixed by: - Using useRef for onFilterChange to maintain a stable reference - Using functional state update (setFilters callback) to access previous filters without needing it as a dependency This prevents stale closures while avoiding infinite re-render loops that would occur if these values were added directly to the dep array. Co-Authored-By: Claude Opus 4.6 --- apps/web/src/components/filters/FilterBar.tsx | 29 ++++++++++++------- docs/claude/orchestrator.md | 6 ++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/filters/FilterBar.tsx b/apps/web/src/components/filters/FilterBar.tsx index e5c7757..981060d 100644 --- a/apps/web/src/components/filters/FilterBar.tsx +++ b/apps/web/src/components/filters/FilterBar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { TaskStatus, TaskPriority } from "@mosaic/shared"; export interface FilterValues { @@ -29,19 +29,28 @@ export function FilterBar({ const [showStatusDropdown, setShowStatusDropdown] = useState(false); const [showPriorityDropdown, setShowPriorityDropdown] = useState(false); + // Stable ref for onFilterChange to avoid re-triggering the debounce effect + const onFilterChangeRef = useRef(onFilterChange); + useEffect(() => { + onFilterChangeRef.current = onFilterChange; + }, [onFilterChange]); + // Debounced search useEffect(() => { const timer = setTimeout(() => { - if (searchValue !== filters.search) { - const newFilters = { ...filters }; - if (searchValue) { - newFilters.search = searchValue; - } else { - delete newFilters.search; + setFilters((prevFilters) => { + if (searchValue !== prevFilters.search) { + const newFilters = { ...prevFilters }; + if (searchValue) { + newFilters.search = searchValue; + } else { + delete newFilters.search; + } + onFilterChangeRef.current(newFilters); + return newFilters; } - setFilters(newFilters); - onFilterChange(newFilters); - } + return prevFilters; + }); }, debounceMs); return (): void => { diff --git a/docs/claude/orchestrator.md b/docs/claude/orchestrator.md index 10dcb0c..977145a 100644 --- a/docs/claude/orchestrator.md +++ b/docs/claude/orchestrator.md @@ -38,6 +38,12 @@ The orchestrator **cold-starts** with just a review report location and minimal **If you find yourself about to edit source code, STOP.** Spawn a worker instead. No exceptions. No "quick fixes." +**Worker Limits:** + +- Maximum **2 parallel workers** at any time +- Wait for at least one worker to complete before spawning more +- This optimizes token usage and reduces context pressure + --- ## Bootstrap Templates