feat(FN-4011): align room input keyboard handlers

Completes Step 1 of mobile touch-focus parity by aligning room input keyboard handlers in ChatView, with a regression test covering mobile room and direct composer touch-focus behavior.

Fusion-Task-Id: FN-4011
This commit is contained in:
Fusion
2026-05-11 08:48:45 -07:00
committed by gsxdsm
parent 0a0f0b99ff
commit bf4786e550
7 changed files with 81 additions and 7 deletions

View File

@@ -1612,7 +1612,6 @@ function RunsTab({
}
}, [initialRunId, preferActiveRun, runs, isLoadingRuns, handleRunClick]);
const handleStopRun = async () => {
const shouldStop = await confirm({
title: "Stop Active Run",
@@ -1633,7 +1632,6 @@ function RunsTab({
}
};
if (isLoadingRuns && runs.length === 0) {
return (
<div className="runs-tab">

View File

@@ -2164,6 +2164,17 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
value={messageInput}
onChange={handleInputChange}
onKeyDown={handleInputKeyDown}
onKeyUp={handleInputKeyUp}
onClick={handleInputSelectionChange}
onBlur={handleInputBlur}
onFocus={handleInputFocus}
onTouchStart={(event) => {
if (typeof window === "undefined") return;
if (window.innerWidth > 768) return;
if (document.activeElement === event.currentTarget) return;
event.preventDefault();
event.currentTarget.focus({ preventScroll: true });
}}
rows={1}
data-testid="chat-input"
/>

View File

@@ -283,6 +283,7 @@ describe("AgentDetailView", () => {
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
} as any);
mockStartAgentRun.mockResolvedValue({ id: "run-003" } as any);
// Default: no budget limit configured
mockFetchAgentBudgetStatus.mockResolvedValue({
agentId: "agent-001",

View File

@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { act, render, screen, waitFor, within } from "@testing-library/react";
import { act, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event";
import { ChatView } from "../ChatView";
import * as useChatModule from "../../hooks/useChat";
@@ -231,6 +231,40 @@ describe("ChatView — rooms (FN-3805..FN-3811 contract)", () => {
mediaSpy.mockRestore();
});
it("keeps room composer touch-focus behavior in parity with direct chat on mobile", async () => {
const mediaSpy = mockMobileViewport();
setup(
{
activeSession,
messages: [{ id: "msg-1", sessionId: activeSession.id, role: "assistant", content: "Direct hello", createdAt: "2026-04-08T00:00:00.000Z" }],
},
{
activeRoom: roomA,
messages: [{ id: "rmsg-1", roomId: roomA.id, role: "assistant", content: "Room hello", createdAt: "2026-04-08T00:00:00.000Z", senderAgentId: "agent-1", mentions: [] }],
},
);
render(<ChatView projectId="proj-123" addToast={vi.fn()} experimentalFeatures={{ chatRooms: true }} />);
const roomInput = screen.getByTestId("chat-input") as HTMLTextAreaElement;
const roomFocusSpy = vi.spyOn(roomInput, "focus");
await act(async () => {
fireEvent.touchStart(roomInput);
});
expect(roomFocusSpy).toHaveBeenCalledWith({ preventScroll: true });
await userEvent.click(screen.getByTestId("chat-sidebar-scope-direct"));
const directInput = screen.getByTestId("chat-input") as HTMLTextAreaElement;
const directFocusSpy = vi.spyOn(directInput, "focus");
await act(async () => {
fireEvent.touchStart(directInput);
});
expect(directFocusSpy).toHaveBeenCalledWith({ preventScroll: true });
mediaSpy.mockRestore();
});
it("applies keyboard-active thread layout in room mode on mobile and preserves direct-chat parity", async () => {
const mediaSpy = mockMobileViewport();
const { listeners, mockVV } = mockMobileVisualViewport({ innerHeight: 800, vvHeight: 800 });