From 0ddbe355113c8a88a93755d34a5826f6dd5a4f3f Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 18 Jul 2026 01:43:20 -0700 Subject: [PATCH] fix: close QuickEntry agent picker outside click; quarantine CLI cascade Capture-phase outside mousedown and an open-token stop the agent portal from staying open or reopening after dismiss. Quarantine the full-suite shard-4 CLI package-lane cascade (extension-dist-barrel hookTimeout + lock-retry timeouts) on sight per the deletion ratchet. --- .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 000000000..41756a026 --- /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 6c0784f48..62071903d 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 7692cea84..48b86b6f2 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 1287fcb05..df017245b 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 0a1c50ef3..6eaf6f2d7 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" } ] }