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();
});