FN-069: show agent mention dropdown above composer
Agent mention suggestions now consistently open above the chat composer in direct and room conversations. - Set a shared above-composer position for both mention popup render paths. - Extend direct-chat and room interaction coverage for popup placement and active conversation selection. Files changed: packages/dashboard/app/components/ChatView.tsx | 10 +++++-- .../__tests__/ChatView.core-interactions.test.tsx | 31 ++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-069 Fusion-Task-Lineage: 69344c00-c076-4299-8edd-528378812309 Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
await userEvent.click(screen.getByTestId("chat-session-session-001"));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Talk to @Alpha and @Unknown next\./)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user