FN-5876: prevent mobile quick-entry autofocus

Avoid restoring focus to the new-task quick entry on mobile while preserving desktop behavior.

- gate quick-entry autofocus and post-submit focus restoration behind a desktop-width check
- add desktop and mobile coverage for initial focus behavior
- add mobile coverage for avoiding focus restoration after successful task creation

Files changed:
 .../dashboard/app/components/QuickEntryBox.tsx     |  5 ++-
 .../components/__tests__/QuickEntryBox.test.tsx    | 42 ++++++++++++++++++++++
 2 files changed, 46 insertions(+), 1 deletion(-)

Fusion-Task-Id: FN-5876

Fusion-Task-Lineage: ede08207-6506-4f5e-8a6f-f7f1cbfb37c9
This commit is contained in:
gsxdsm
2026-06-02 08:08:01 -07:00
parent 91784d9acf
commit 3989193cf2
2 changed files with 46 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ import { NodeHealthDot } from "./NodeHealthDot";
import { ProviderIcon } from "./ProviderIcon";
const STORAGE_KEY = "kb-quick-entry-text";
const MOBILE_BREAKPOINT_PX = 768;
const ALLOWED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
interface PendingImage {
@@ -329,7 +330,9 @@ export function QuickEntryBox({ onCreate, addToast, tasks = [], availableModels,
if (!isSubmitting && description === "" && textareaRef.current) {
// Use setTimeout to ensure focus happens after React re-enables the textarea
const focusTimeout = setTimeout(() => {
textareaRef.current?.focus();
if (typeof window !== "undefined" && window.innerWidth > MOBILE_BREAKPOINT_PX) {
textareaRef.current?.focus();
}
}, 0);
return () => clearTimeout(focusTimeout);
}

View File

@@ -295,6 +295,28 @@ describe("QuickEntryBox", () => {
expect((textarea as HTMLTextAreaElement).rows).toBe(2);
});
it("focuses the quick-entry textarea on mount at desktop width", async () => {
mockDesktopViewport();
renderQuickEntryBox({});
const textarea = screen.getByTestId("quick-entry-input");
await waitFor(() => {
expect(document.activeElement).toBe(textarea);
});
});
it("does not focus the quick-entry textarea on mount at mobile width", async () => {
const innerWidthSpy = vi.spyOn(window, "innerWidth", "get").mockReturnValue(375);
renderQuickEntryBox({});
const textarea = screen.getByTestId("quick-entry-input");
await waitFor(() => {
expect(document.activeElement).not.toBe(textarea);
});
innerWidthSpy.mockRestore();
});
it("textarea spans full container width (FN-1608)", () => {
mockDesktopViewport();
renderQuickEntryBox({});
@@ -643,6 +665,26 @@ describe("QuickEntryBox", () => {
});
});
it("does not restore focus after successful creation at mobile width", async () => {
const innerWidthSpy = vi.spyOn(window, "innerWidth", "get").mockReturnValue(375);
const { props } = renderQuickEntryBox({});
const textarea = screen.getByTestId("quick-entry-input");
fireEvent.focus(textarea);
fireEvent.change(textarea, { target: { value: "Task to create" } });
fireEvent.keyDown(textarea, { key: "Enter" });
await waitFor(() => {
expect(props.onCreate).toHaveBeenCalled();
});
await waitFor(() => {
expect(document.activeElement).not.toBe(textarea);
});
innerWidthSpy.mockRestore();
});
describe("Rich creation features", () => {
it("shows inline deps/models/save controls when expanded", () => {
renderQuickEntryBox({});