diff --git a/.changeset/fn-7462-mobile-chat-dropdown-title.md b/.changeset/fn-7462-mobile-chat-dropdown-title.md
new file mode 100644
index 0000000000..431b6206a3
--- /dev/null
+++ b/.changeset/fn-7462-mobile-chat-dropdown-title.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Show conversation titles in the mobile Chat dropdown.
+category: fix
+dev: Keeps the provider logo while removing model-name text from the mobile direct-chat trigger.
diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md
index 2b95d774dc..f02bce9fca 100644
--- a/docs/dashboard-guide.md
+++ b/docs/dashboard-guide.md
@@ -412,7 +412,7 @@ Chat view provides project-scoped conversations with agents.
- If you queue follow-up user messages while the assistant is still streaming, Chat persists them per session, stacks each queued preview above the input box with one shared divider, and restores/sends them one at a time in FIFO order once each active response finishes if you leave and return.
- Chat message lists now track near-bottom scroll state: while you are reading older messages, live streaming/new replies do not force-scroll; a **Latest** jump control appears until you return to the tail.
- On mobile direct-chat threads, entering a thread and restoring Chat after tab/page visibility returns re-anchors to the newest message (`scrollTop = scrollHeight`) so the view always opens at the live tail.
-- On mobile direct-chat threads, the top Chat header collapses into one compact row: the back button is the far-left visible control and the active conversation dropdown stays beside it, while the visible Chat icon/title shell is hidden to preserve transcript space. Tapping the active conversation opens a lightweight dropdown so you can switch to another direct session or start a New Chat without backing out to the sidebar list first; long conversation titles stay readable in the dropdown via wrapped option text and taller touch-friendly rows.
+- On mobile direct-chat threads, the top Chat header collapses into one compact row: the back button is the far-left visible control and the active conversation dropdown stays beside it, while the visible Chat icon/title shell is hidden to preserve transcript space. The dropdown trigger shows the conversation title with the provider/model logo only (no model-name text), and tapping it opens a lightweight dropdown so you can switch to another direct session or start a New Chat without backing out to the sidebar list first; long conversation titles stay readable in the dropdown via wrapped option text and taller touch-friendly rows.
- On mobile direct-chat threads, the single thread-wide Markdown/plain eye toggle floats above the transcript/composer area instead of occupying a second header row; desktop/tablet keeps the toggle in the thread header.
- Direct chat sessions can be renamed from the sidebar row edit button, the desktop conversation context menu, and the mobile session switcher; blank rename submissions clear the custom title so the default session label is shown again.
diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css
index 9217463b54..f41cf37961 100644
--- a/packages/dashboard/app/components/ChatView.css
+++ b/packages/dashboard/app/components/ChatView.css
@@ -615,14 +615,6 @@ The Direct-chat context budget indicator is a secondary desktop/tablet header af
white-space: nowrap;
}
-.chat-mobile-session-trigger .chat-model-tag {
- flex-shrink: 0;
- max-width: calc(var(--space-xl) * 4);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
.chat-mobile-session-trigger .provider-icon,
.chat-mobile-session-trigger > svg:first-child {
flex-shrink: 0;
diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx
index 8ba0e85de9..e6af84f660 100644
--- a/packages/dashboard/app/components/ChatView.tsx
+++ b/packages/dashboard/app/components/ChatView.tsx
@@ -2139,6 +2139,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
const threadHeaderTitle = activeSession?.agentId === FN_AGENT_ID
? (activeModelTag ?? "Fusion")
: activeSession?.title || agentsMap.get(activeSession?.agentId ?? "")?.name || activeSession?.agentId || "Chat";
+ const mobileDirectSessionTitle = activeSession?.title || t("chat.untitledSession", "Untitled");
const showThreadHeaderModelTag = Boolean(activeModelTag && activeModelTag !== threadHeaderTitle);
const showThreadHeaderContextWindow = !isChatMobile && hasThreadInView && activeContextWindow !== null;
@@ -2590,8 +2591,11 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
onClick={() => setMobileSessionMenuOpen((open) => !open)}
>
{activeModelProvider ? : }
- {threadHeaderTitle}
- {showThreadHeaderModelTag && {activeModelTag}}
+ {/*
+ FNXC:ChatHeader 2026-07-03-00:00:
+ Mobile direct-chat needs one compact, conversation-oriented dropdown trigger. Show the active conversation title (or Untitled) beside only the provider/model logo and chevron; keep model-name badges exclusive to the desktop/thread identity row.
+ */}
+ {mobileDirectSessionTitle}
{mobileSessionMenuOpen && (
diff --git a/packages/dashboard/app/components/__tests__/ChatView.core-contracts.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.core-contracts.test.tsx
index 9989fda588..85877d0a02 100644
--- a/packages/dashboard/app/components/__tests__/ChatView.core-contracts.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ChatView.core-contracts.test.tsx
@@ -346,16 +346,27 @@ describe("Chat Session Delete Button", () => {
const restoreMatchMedia = mockViewportMode("mobile");
const renameSession = vi.fn().mockResolvedValue(undefined);
try {
+ const initialSession: ChatSessionInfo = {
+ id: "session-001",
+ agentId: "__fn_agent__",
+ status: "active",
+ title: "Mobile Chat",
+ modelProvider: "minimax",
+ modelId: "m3",
+ createdAt: "2026-04-08T00:00:00.000Z",
+ updatedAt: "2026-04-08T00:00:00.000Z",
+ };
setupMockChat({
- activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Mobile Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" },
- sessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Mobile Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" }],
- filteredSessions: [{ id: "session-001", agentId: "agent-001", status: "active", title: "Mobile Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" }],
+ activeSession: initialSession,
+ sessions: [initialSession],
+ filteredSessions: [initialSession],
renameSession,
});
const view = await renderWithAct();
expect(screen.getByTestId("chat-mobile-session-trigger")).toHaveTextContent("Mobile Chat");
+ expect(screen.getByTestId("chat-mobile-session-trigger")).not.toHaveTextContent("M3");
await userEvent.click(screen.getByTestId("chat-mobile-session-trigger"));
await userEvent.click(screen.getByTestId("chat-mobile-session-rename-session-001"));
@@ -367,7 +378,7 @@ describe("Chat Session Delete Button", () => {
expect(renameSession).toHaveBeenCalledWith("session-001", "Mobile Renamed");
- const renamedSession: ChatSessionInfo = { id: "session-001", agentId: "agent-001", status: "active", title: "Mobile Renamed", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" };
+ const renamedSession: ChatSessionInfo = { ...initialSession, title: "Mobile Renamed" };
setupMockChat({
activeSession: renamedSession,
sessions: [renamedSession],
@@ -378,7 +389,10 @@ describe("Chat Session Delete Button", () => {
view.rerender();
});
- expect(screen.getByTestId("chat-mobile-session-trigger")).toHaveTextContent("Mobile Renamed");
+ const trigger = screen.getByTestId("chat-mobile-session-trigger");
+ expect(trigger).toHaveTextContent("Mobile Renamed");
+ expect(trigger).not.toHaveTextContent("M3");
+ expect(trigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
const headerTitle = document.querySelector(".chat-thread-header-title") as HTMLElement | null;
expect(headerTitle).toHaveTextContent("Mobile Renamed");
} finally {
@@ -606,6 +620,7 @@ describe("ChatView CSS — mobile thread switcher", () => {
expect(optionTitleMatch?.[1]).toContain("line-height: normal");
expect(optionTitleMatch?.[1]).toContain("white-space: normal");
expect(optionTitleMatch?.[1]).toContain("overflow-wrap: anywhere");
+ expect(css).not.toMatch(/\.chat-mobile-session-trigger\s+\.chat-model-tag/);
});
it("keeps mobile override for header identity overflow visible so dropdown can render", async () => {
diff --git a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx
index 643eee4a14..44c0cbf503 100644
--- a/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ChatView.mobile.test.tsx
@@ -268,12 +268,48 @@ describe("ChatView mobile behavior", () => {
expect(backButton.compareDocumentPosition(sessionTrigger) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
expect(headerTitle).not.toContainElement(backButton);
expect(headerTitle.querySelector("svg")).toHaveAttribute("aria-hidden", "true");
- expect(sessionTrigger).toHaveTextContent("M3");
+ expect(sessionTrigger).toHaveTextContent("Testing");
+ expect(sessionTrigger).not.toHaveTextContent("M3");
+ expect(sessionTrigger).not.toHaveTextContent("MiniMax M3");
+ expect(within(sessionTrigger).getByTestId("minimax-icon")).toBeInTheDocument();
+ expect(sessionTrigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
expect(viewHeader).not.toContainElement(renderToggle);
expect(renderToggle).toHaveClass("chat-thread-header-render-toggle--floating");
expect(screen.getAllByTestId("chat-back-btn")).toHaveLength(1);
expect(screen.getAllByTestId("chat-mobile-session-trigger")).toHaveLength(1);
expect(screen.getByRole("heading", { name: "Chat" })).toBeInTheDocument();
+ expect(document.querySelector(".chat-thread-header")).not.toBeInTheDocument();
+ } finally {
+ restoreMatchMedia.mockRestore();
+ }
+ });
+
+ it("mobile mode: model-only empty title uses Untitled without model-name text", async () => {
+ const restoreMatchMedia = mockMobileViewport();
+ try {
+ setupMockChat({
+ activeSession: {
+ id: "session-001",
+ agentId: "__fn_agent__",
+ status: "active",
+ title: null,
+ modelProvider: "minimax",
+ modelId: "m3",
+ createdAt: "2026-04-08T00:00:00.000Z",
+ updatedAt: "2026-04-08T00:00:00.000Z",
+ },
+ messages: [{ id: "msg-001", sessionId: "session-001", role: "assistant", content: "Hello", createdAt: "2026-04-08T00:00:00.000Z" }],
+ });
+
+ await renderWithAct();
+
+ const trigger = screen.getByTestId("chat-mobile-session-trigger");
+ expect(trigger).toHaveTextContent("Untitled");
+ expect(trigger).not.toHaveTextContent("M3");
+ expect(trigger).not.toHaveTextContent("MiniMax M3");
+ expect(within(trigger).getByTestId("minimax-icon")).toBeInTheDocument();
+ expect(trigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
+ expect(screen.getAllByTestId("chat-mobile-session-trigger")).toHaveLength(1);
} finally {
restoreMatchMedia.mockRestore();
}
@@ -429,6 +465,7 @@ describe("ChatView mobile behavior", () => {
expect(headerActions.firstElementChild).toBe(backButton);
expect(trigger).toHaveTextContent(longTitle);
expect(within(trigger).getByTestId("icon-bot")).toBeInTheDocument();
+ expect(trigger.querySelector(".chat-model-tag")).not.toBeInTheDocument();
expect(screen.getAllByTestId("chat-mobile-session-trigger")).toHaveLength(1);
await userEvent.click(trigger);