diff --git a/.changeset/fix-quick-add-model-filter.md b/.changeset/fix-quick-add-model-filter.md
new file mode 100644
index 0000000000..b47f22fcc5
--- /dev/null
+++ b/.changeset/fix-quick-add-model-filter.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Fix the Quick Add model dropdown filter box so typing narrows the model list.
+category: fix
+dev: The quick-entry model menu's blanket onMouseDown preventDefault crossed the React portal boundary and suppressed focus on CustomModelDropdown's search input.
diff --git a/packages/dashboard/app/components/QuickEntryBox.tsx b/packages/dashboard/app/components/QuickEntryBox.tsx
index 414b3bb544..12b05ee9fe 100644
--- a/packages/dashboard/app/components/QuickEntryBox.tsx
+++ b/packages/dashboard/app/components/QuickEntryBox.tsx
@@ -2402,7 +2402,19 @@ export function QuickEntryBox({ onCreate, onMoveTask, addToast, tasks = [], avai
e.preventDefault()}
+ onMouseDown={(e) => {
+ /*
+ FNXC:QuickAddModels 2026-08-12-21:51:
+ React synthetic mouse events cross createPortal boundaries, so this composer-focus guard
+ must exempt form controls and the portaled model dropdown or preventDefault suppresses
+ focus for its filter input. Plain menu chrome still preserves the quick-entry focus.
+ */
+ const target = e.target as Element;
+ if (target.closest("input, textarea, select, [contenteditable], .model-combobox-dropdown--portal")) {
+ return;
+ }
+ e.preventDefault();
+ }}
data-testid="model-nested-menu"
style={{
position: "fixed",
diff --git a/packages/dashboard/app/components/__tests__/QuickEntryBox-model-filter.test.tsx b/packages/dashboard/app/components/__tests__/QuickEntryBox-model-filter.test.tsx
new file mode 100644
index 0000000000..8e759ca64a
--- /dev/null
+++ b/packages/dashboard/app/components/__tests__/QuickEntryBox-model-filter.test.tsx
@@ -0,0 +1,113 @@
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { QuickEntryBox } from "../QuickEntryBox";
+import type { ModelInfo } from "../../api";
+
+vi.mock("../../api", () => ({
+ fetchModels: vi.fn(),
+ fetchSettings: vi.fn().mockResolvedValue({
+ modelPresets: [],
+ autoSelectModelPreset: false,
+ defaultPresetBySize: {},
+ maxConcurrent: 2,
+ maxWorktrees: 4,
+ pollIntervalMs: 30_000,
+ groupOverlappingFiles: true,
+ autoMerge: true,
+ }),
+ fetchAgents: vi.fn().mockResolvedValue([]),
+ checkDuplicateTasks: vi.fn().mockResolvedValue([]),
+ uploadAttachment: vi.fn().mockResolvedValue({}),
+ fetchWorkflowOptionalSteps: vi.fn().mockResolvedValue([]),
+ updateGlobalSettings: vi.fn().mockResolvedValue({}),
+}));
+
+vi.mock("../../hooks/useNodes", () => ({
+ useNodes: () => ({
+ nodes: [],
+ loading: false,
+ error: null,
+ refresh: vi.fn(),
+ register: vi.fn(),
+ update: vi.fn(),
+ unregister: vi.fn(),
+ healthCheck: vi.fn(),
+ }),
+}));
+
+vi.mock("../../hooks/useComposerDictation", () => ({
+ useComposerDictation: () => ({ micProps: { enabled: false, supported: false, state: "idle", start: vi.fn(), stop: vi.fn() } }),
+}));
+
+const models: ModelInfo[] = [
+ { provider: "anthropic", id: "claude-sonnet", name: "Claude Sonnet", reasoning: true, contextWindow: 200_000 },
+ { provider: "openai", id: "gpt-4o", name: "GPT-4o", reasoning: false, contextWindow: 128_000 },
+];
+
+const originalInnerWidth = Object.getOwnPropertyDescriptor(window, "innerWidth");
+
+function setViewport(width: number) {
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: width });
+}
+
+function renderQuickEntry() {
+ return render(
+ ,
+ );
+}
+
+/**
+ * FNXC:QuickAddModels 2026-08-12-21:51:
+ * This regression uses the real portaled CustomModelDropdown because a stub cannot prove that
+ * a React portal ancestor's mousedown handling preserves focus for the nested filter input.
+ */
+describe("QuickEntryBox model filter", () => {
+ beforeEach(() => {
+ localStorage.clear();
+ document.body.innerHTML = "";
+ });
+
+ afterEach(() => {
+ if (originalInnerWidth) {
+ Object.defineProperty(window, "innerWidth", originalInnerWidth);
+ }
+ localStorage.clear();
+ });
+
+ it.each([
+ { lane: "executor", width: 1280, query: "clau", expected: "Claude Sonnet", absent: "GPT-4o" },
+ { lane: "validator", width: 375, query: "no-match", expected: null, absent: "Claude Sonnet" },
+ ])("focuses and filters the $lane model dropdown at $widthpx", async ({ lane, width, query, expected, absent }) => {
+ setViewport(width);
+ const user = userEvent.setup();
+ renderQuickEntry();
+
+ const textarea = screen.getByTestId("quick-entry-input") as HTMLTextAreaElement;
+ await user.type(textarea, "Keep this description");
+ await user.click(screen.getByTestId("quick-entry-models"));
+ await user.click(screen.getByTestId(`model-menu-${lane}`));
+ await user.click(screen.getByRole("button", { name: `${lane} model` }));
+
+ const filterInput = await screen.findByPlaceholderText("Filter models…");
+ await user.click(filterInput);
+ expect(document.activeElement).toBe(filterInput);
+ await user.keyboard(query);
+
+ expect(filterInput).toHaveValue(query);
+ expect(screen.getByTestId("model-nested-menu")).toBeInTheDocument();
+ expect(textarea).toHaveValue("Keep this description");
+
+ if (expected) {
+ expect(screen.getByText(expected)).toBeInTheDocument();
+ } else {
+ expect(screen.getByText(/No models match/)).toBeInTheDocument();
+ }
+ expect(screen.queryByText(absent)).not.toBeInTheDocument();
+ });
+});