diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index e9d40bdb34..7e972208ab 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -70,6 +70,12 @@ import { type ChatInputAutosizeController, } from "../utils/chatInputAutosize"; +/* +FNXC:AgentMentionPopup 2026-08-20-04:49: +FN-069 requires @ suggestions to open above the composer like / skills because below-composer modal placement can hide them. Direct and room composers share this invariant. +*/ +const AGENT_MENTION_POPUP_POSITION = "above" as const; + /** * Optional task-bound context that enables the "/" command registry (e.g. * `/steer`) in a ChatView instance. When omitted (the default for the @@ -3017,7 +3023,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout highlightedIndex={mentionHighlightIndex} visible={mentionPopupVisible} onSelect={handleMentionSelect} - position="below" + position={AGENT_MENTION_POPUP_POSITION} roomMemberIds={roomContext?.memberIds} roomName={roomContext?.roomName} /> @@ -3835,7 +3841,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout highlightedIndex={mentionHighlightIndex} visible={mentionPopupVisible} onSelect={handleMentionSelect} - position="below" + position={AGENT_MENTION_POPUP_POSITION} roomMemberIds={roomContext?.memberIds} roomName={roomContext?.roomName} /> diff --git a/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx index 25f22ec162..1ce5721f86 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.core-interactions.test.tsx @@ -419,15 +419,16 @@ describe("ChatView core interactions", () => { }); describe("agent mentions", () => { - it("shows mention popup when @ is typed", async () => { + it("opens direct-chat mention popup above the composer when @ is typed", async () => { setupMockChat({ activeSession: activeSessionFixture, messages: [] }); await renderWithAct(); + await userEvent.click(screen.getByTestId("chat-session-session-001")); const textarea = screen.getByTestId("chat-input"); await userEvent.type(textarea, "@"); - expect(await screen.findByTestId("agent-mention-popup")).toBeInTheDocument(); + expect(await screen.findByTestId("agent-mention-popup")).toHaveClass("agent-mention-popup--above"); }); it("filters mention popup by text after @", async () => { @@ -435,6 +436,7 @@ describe("ChatView core interactions", () => { await renderWithAct(); + await userEvent.click(screen.getByTestId("chat-session-session-001")); const textarea = screen.getByTestId("chat-input"); await userEvent.type(textarea, "@be"); @@ -447,6 +449,7 @@ describe("ChatView core interactions", () => { await renderWithAct(); + await userEvent.click(screen.getByTestId("chat-session-session-001")); const textarea = screen.getByTestId("chat-input"); await userEvent.type(textarea, "@"); expect(await screen.findByTestId("agent-mention-popup")).toBeInTheDocument(); @@ -460,6 +463,7 @@ describe("ChatView core interactions", () => { await renderWithAct(); + await userEvent.click(screen.getByTestId("chat-session-session-001")); const textarea = screen.getByTestId("chat-input") as HTMLTextAreaElement; await userEvent.type(textarea, "@al"); @@ -472,16 +476,18 @@ describe("ChatView core interactions", () => { it("uses room member ordering in popup and marks non-member mention chips in room messages", async () => { setupMockChat({ activeSession: activeSessionFixture, messages: [] }); + const activeRoom = { + id: "room-001", + slug: "engineering", + name: "engineering", + createdBy: "agent-001", + status: "active" as const, + createdAt: "2026-04-08T00:00:00.000Z", + updatedAt: "2026-04-08T00:00:00.000Z", + }; setupMockRooms({ - activeRoom: { - id: "room-001", - slug: "engineering", - name: "engineering", - createdBy: "agent-001", - status: "active", - createdAt: "2026-04-08T00:00:00.000Z", - updatedAt: "2026-04-08T00:00:00.000Z", - }, + rooms: [activeRoom], + activeRoom, activeRoomMembers: [ { roomId: "room-001", agentId: "agent-001", role: "member", addedAt: "2026-04-08T00:00:00.000Z" }, ], @@ -509,9 +515,11 @@ describe("ChatView core interactions", () => { const user = userEvent.setup({ delay: null }); await user.click(screen.getByTestId("chat-sidebar-scope-rooms")); + await user.click(screen.getByTestId("chat-room-item-engineering")); const textarea = screen.getByTestId("chat-input"); await user.type(textarea, "@"); + expect(await screen.findByTestId("agent-mention-popup")).toHaveClass("agent-mention-popup--above"); expect(screen.getByTestId("agent-mention-members-header")).toBeInTheDocument(); expect(screen.queryByTestId("agent-mention-others-header")).not.toBeInTheDocument(); @@ -544,6 +552,7 @@ describe("ChatView core interactions", () => { await renderWithAct(); + await userEvent.click(screen.getByTestId("chat-session-session-001")); await waitFor(() => { expect(screen.getByText(/Talk to @Alpha and @Unknown next\./)).toBeInTheDocument(); });