From c0cce18cfb0d0a2b462949fdd1f96c48985f2f52 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 18 Jul 2026 01:54:27 -0700 Subject: [PATCH] fix: QuickEntry agent outside-click + quarantine CLI full-suite cascade (#2291) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Latest Full Suite after #2290 was green on shards 1–2 and nearly green on 3–4: - **Shard 3:** QuickEntry agent picker outside click left the portal open (product) — capture-phase mousedown + open-token so late `fetchAgents` cannot re-open a dismissed picker - **Shard 4:** `@runfusion/fusion` package-lane cascade (87 failures from `extension-dist-barrel` hookTimeout + lock-retry timeouts under load) — quarantine the 14 observed files on sight (ledger + vitest exclude), no timeout appeasement Also hardens the agent-picker outside-click test. ## Test plan - [x] Local agent picker portal tests green - [ ] PR gate - [ ] Post-merge Full Suite green ## Summary by CodeRabbit - **Bug Fixes** - Fixed the Quick Add agent picker so it reliably closes when clicking outside. - Prevented delayed agent-loading results from reopening the picker after it has been dismissed. - Improved the picker’s loading behavior by displaying it immediately while agents are being retrieved. - **Tests** - Added coverage for dismissing the agent picker with an outside click. --- .changeset/quickentry-agent-outside-click.md | 7 ++ packages/cli/vitest.config.ts | 21 ++++++ .../app/components/QuickEntryBox.tsx | 30 +++++++- .../__tests__/QuickEntryBox.test.tsx | 16 +++-- scripts/lib/test-quarantine.json | 72 ++++++++++++++++++- 5 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 .changeset/quickentry-agent-outside-click.md diff --git a/.changeset/quickentry-agent-outside-click.md b/.changeset/quickentry-agent-outside-click.md new file mode 100644 index 0000000000..41756a0269 --- /dev/null +++ b/.changeset/quickentry-agent-outside-click.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Close the Quick Add agent picker when clicking outside it. +category: fix +dev: Capture-phase mousedown listener plus open-token so late fetchAgents cannot re-open a dismissed portal. diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts index 6c0784f487..62071903da 100644 --- a/packages/cli/vitest.config.ts +++ b/packages/cli/vitest.config.ts @@ -98,6 +98,27 @@ const quarantinedCliTests: string[] = [ FNXC:CliTests 2026-07-16-09:00: FN-8077 removed project-context.test.ts from this list and the ledger in lockstep. Its CentralCore coverage now uses the external PostgreSQL test harness under pgDescribe rather than launching an embedded postmaster for each test in forked loaded lanes; pure formatting coverage remains ungated. */ + /* + FNXC:CliTests 2026-07-18-08:50: + Full-suite shard 4 (runs 29637256028, 29637376023) re-exposed package-lane cascade: + extension-dist-barrel beforeAll hookTimeout under PG load, plus lock-retry timeouts, + cascading 87 failures. Quarantine the observed integration-heavy files on sight in + lockstep with scripts/lib/test-quarantine.json — do not raise hookTimeout/testTimeout. + */ + "src/__tests__/bin.test.ts", + "src/__tests__/extension-agent-update.test.ts", + "src/__tests__/extension-dist-barrel.test.ts", + "src/__tests__/extension-goal-tools.test.ts", + "src/__tests__/extension-mission-goal-tools.test.ts", + "src/__tests__/extension-tool-timeout.test.ts", + "src/__tests__/extension-workflow-tools.test.ts", + "src/__tests__/extension.test.ts", + "src/__tests__/task-plan.test.ts", + "src/commands/__tests__/mcp-lock-retry.test.ts", + "src/commands/__tests__/plugin.test.ts", + "src/commands/__tests__/task-lock-retry.test.ts", + "src/commands/dashboard-tui/__tests__/app.test.tsx", + "src/commands/dashboard-tui/__tests__/terminal-attach.test.ts", ]; export default defineConfig({ diff --git a/packages/dashboard/app/components/QuickEntryBox.tsx b/packages/dashboard/app/components/QuickEntryBox.tsx index 7692cea84b..48b86b6f2c 100644 --- a/packages/dashboard/app/components/QuickEntryBox.tsx +++ b/packages/dashboard/app/components/QuickEntryBox.tsx @@ -191,6 +191,8 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, const modelMenuPortalRef = useRef(null); const agentPickerRef = useRef(null); const agentPickerPortalRef = useRef(null); + /** Bumps on open/close so a late fetchAgents resolution cannot re-open a dismissed picker. */ + const agentPickerOpenTokenRef = useRef(0); const nodePickerRef = useRef(null); const nodePickerPortalRef = useRef(null); const priorityPickerRef = useRef(null); @@ -554,17 +556,25 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, useEffect(() => { if (!showAgentPicker) return; + /* + FNXC:QuickEntry 2026-07-18-08:45: + Listen in the capture phase so outside mousedown closes the agent portal even + when a nested control calls preventDefault/stopPropagation on bubble. Full- + suite + local tests observed the bubble-only listener leaving "Select agent" + mounted after a true outside click. + */ const handleClickOutside = (e: MouseEvent) => { const target = e.target as Node; // Check both the trigger button and the portaled dropdown if (agentPickerRef.current?.contains(target)) return; if (agentPickerPortalRef.current?.contains(target)) return; + agentPickerOpenTokenRef.current += 1; setShowAgentPicker(false); setAgentPickerPosition(null); }; - document.addEventListener("mousedown", handleClickOutside); - return () => document.removeEventListener("mousedown", handleClickOutside); + document.addEventListener("mousedown", handleClickOutside, true); + return () => document.removeEventListener("mousedown", handleClickOutside, true); }, [showAgentPicker]); useEffect(() => { @@ -1637,24 +1647,38 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels, const loadAgents = useCallback(async () => { if (agents.length > 0 && agentsProjectId === projectId) { + agentPickerOpenTokenRef.current += 1; setShowAgentPicker(true); updateAgentPickerPosition(); return; } + /* + FNXC:QuickEntry 2026-07-18-08:45: + Open the picker immediately (with loading) so outside-click listeners arm + before fetchAgents resolves. Bump a token on open/close so a late fetch + does not re-open the portal after the operator dismissed it. + */ + const openToken = ++agentPickerOpenTokenRef.current; setAgentsLoading(true); + setShowAgentPicker(true); + updateAgentPickerPosition(); try { const result = await fetchAgents(undefined, projectId); + if (openToken !== agentPickerOpenTokenRef.current) return; setAgents(result); setAgentsProjectId(projectId); setShowAgentPicker(true); updateAgentPickerPosition(); } catch (err) { + if (openToken !== agentPickerOpenTokenRef.current) return; const msg = getErrorMessage(err); addToast(msg ? t("tasks.loadAgentsFailed", "Failed to load agents: {{msg}}", { msg }) : t("tasks.loadAgentsFailedGeneric", "Failed to load agents"), "error"); setShowAgentPicker(false); } finally { - setAgentsLoading(false); + if (openToken === agentPickerOpenTokenRef.current) { + setAgentsLoading(false); + } } }, [agents.length, agentsProjectId, projectId, addToast, updateAgentPickerPosition]); diff --git a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx index 1287fcb053..df017245b5 100644 --- a/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx +++ b/packages/dashboard/app/components/__tests__/QuickEntryBox.test.tsx @@ -4955,13 +4955,19 @@ describe("QuickEntryBox", () => { const outsideElement = document.createElement("div"); document.body.appendChild(outsideElement); try { - fireEvent.mouseDown(outsideElement); + /* + FNXC:DashboardTests 2026-07-18-08:45: + Dispatch a bubbling native MouseEvent so the document mousedown listener + in QuickEntryBox sees the outside target (RTL fireEvent alone was flaky + under full-suite and failed locally for this agent portal). + */ + outsideElement.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true })); + await waitFor(() => { + expect(screen.queryByText("Select agent")).toBeNull(); + }); } finally { - document.body.removeChild(outsideElement); + if (outsideElement.parentNode) document.body.removeChild(outsideElement); } - - // Picker should be closed - expect(screen.queryByText("Select agent")).toBeNull(); }); it("repositions portaled picker on window resize while open", async () => { diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index 0a1c50ef34..6eaf6f2d77 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -1,5 +1,5 @@ { - "$comment": "Flaky-test quarantine ledger (deletion ratchet — see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date — the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config is the enforcement.", + "$comment": "Flaky-test quarantine ledger (deletion ratchet \u2014 see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date \u2014 the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config is the enforcement.", "entries": [ { "file": "packages/engine/src/__tests__/backlog-pressure-reporter.test.ts", @@ -40,6 +40,76 @@ "file": "packages/engine/src/__tests__/heartbeat-error-recovery.test.ts", "reason": "Full-suite shard 1 timeout (30s) on rotation-shaped 401 recovery under load without product bug evidence. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29636550951. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/bin.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-agent-update.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-dist-barrel.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-goal-tools.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-mission-goal-tools.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-tool-timeout.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension-workflow-tools.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/extension.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/__tests__/task-plan.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/commands/__tests__/mcp-lock-retry.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/commands/__tests__/plugin.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/commands/__tests__/task-lock-retry.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/commands/dashboard-tui/__tests__/app.test.tsx", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" + }, + { + "file": "packages/cli/src/commands/dashboard-tui/__tests__/terminal-attach.test.ts", + "reason": "Full-suite shard 4 package-lane cascade (run 29637376023 / 29637256028): extension-dist-barrel beforeAll hookTimeout 10s + lock-retry 5s timeouts under shard load, cascading 87 failures across CLI integration suites without product-bug evidence. Quarantine on sight per AGENTS.md. Mirrored in packages/cli/vitest.config.ts.", + "quarantinedAt": "2026-07-18" } ] }