diff --git a/.changeset/fn-7651-remove-chat-title-only-toggle.md b/.changeset/fn-7651-remove-chat-title-only-toggle.md new file mode 100644 index 0000000000..1be36da332 --- /dev/null +++ b/.changeset/fn-7651-remove-chat-title-only-toggle.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Removed the chat "Search in title only" toggle; chat search always matches message content and title. +category: fix +dev: Dropped searchInTitleOnly/setSearchInTitleOnly from useChat and the ChatView toggle button; content-search path (q param, matchedMessagePreview) is now always-on. Server GET /chat/sessions titleOnly param retained but unused by the client. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 67abda68f9..7046357541 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -511,8 +511,8 @@ Chat view provides project-scoped conversations with agents. - Editing is truncate-and-resend, not append: the edited message and everything after it are removed first, then the edited text is sent as a new turn through the normal streaming path — so the resulting transcript looks the same as if you had deleted the old messages and typed the correction from scratch, but in one action. - In task-detail **Planner Chat**, editing an earlier message resumes the conversation from that point exactly as in direct chat. If the discarded turns already triggered task-scoped side effects — a steering comment added via the planner steering tool, or a refinement task created via the planner refinement tool — those **are not reverted**: the steering comment stays on the task and the refinement task stays open, because undoing either is destructive and out of scope for a chat edit. After a successful edit-and-resend, task detail refreshes automatically (so Activity/steering reflects reality), and if the discarded range held one of those confirmations you get an informational toast noting that the earlier change was not undone. - -- The Chat sidebar search box matches message **content** by default, not just conversation title/agent — so you can find a past conversation by remembering something that was said in it, even if the title doesn't contain the query. Content matches run as a debounced server-side lookup and are merged with local title/agent matches into the same results list. A **Search in title only** toggle beside the search box restores the original title/agent-only, fully client-side filtering behavior. The toggle is available on both desktop and mobile Chat sidebars and does not appear in the Rooms scope (Rooms already hides search/list). When a session is shown because of a content match, its row shows a subtle "Matched: ..." preview of the matching message so it's clear why the conversation appeared. Task-planner sessions stay excluded from content matches by the same common-feed guard used for the normal session list (see **Settings → Project General → Show task chats in common Chat feed**). + +- The Chat sidebar search box always matches message **content** in addition to conversation title/agent — so you can find a past conversation by remembering something that was said in it, even if the title doesn't contain the query. Content matches run as a debounced server-side lookup and are merged with local title/agent matches into the same results list; there is no toggle to restrict search back to title-only. This applies on both desktop and mobile Chat sidebars and does not appear in the Rooms scope (Rooms already hides search/list). When a session is shown because of a content match, its row shows a subtle "Matched: ..." preview of the matching message so it's clear why the conversation appeared. Task-planner sessions stay excluded from content matches by the same common-feed guard used for the normal session list (see **Settings → Project General → Show task chats in common Chat feed**). ![Chat view](./screenshots/chat-view.png) diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css index d568a8b2fd..fdee42469f 100644 --- a/packages/dashboard/app/components/ChatView.css +++ b/packages/dashboard/app/components/ChatView.css @@ -272,24 +272,6 @@ When the movable chat popup is resized narrow, collapse Direct/Rooms labels to i gap: var(--space-xs); } -/* -FNXC:ChatSearch 2026-07-07-00:00: -Compact toggle button, reusing .btn/.btn-sm so it inherits the shared button visual language -instead of one-off styling. aria-pressed drives the active/inactive look via existing token -colors (no hardcoded hex). -*/ -.chat-search-title-only-toggle { - align-self: flex-start; - font-size: 11px; - color: var(--text-dim); -} - -.chat-search-title-only-toggle[aria-pressed="true"] { - color: var(--text); - border-color: var(--accent); - background: var(--surface-hover); -} - .chat-session-preview--matched { color: var(--text-dim); font-size: 11px; diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx index fc5f922361..10870b53b4 100644 --- a/packages/dashboard/app/components/ChatView.tsx +++ b/packages/dashboard/app/components/ChatView.tsx @@ -494,8 +494,6 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout hasMoreMessages, searchQuery, setSearchQuery, - searchInTitleOnly, - setSearchInTitleOnly, filteredSessions, agentsMap: chatAgentsMap, } = useChat(projectId, addToast); @@ -2788,12 +2786,13 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout <> {/* Search section */} {/* - FNXC:ChatSearch 2026-07-07-00:00: - Search now matches message content by default (server round trip), not just - title/agentId. The "Search in title only" toggle restores the prior client-only - title/agentId behavior on demand. Rendered on both desktop and mobile since this - sidebar markup is shared (mobile layout is a CSS breakpoint of the same DOM), and - only within the Direct scope — Rooms already hides search/list entirely. + FNXC:ChatSearch 2026-07-07-12:00: + Search always matches message content (server round trip) in addition to + title/agentId; there is no client toggle to restrict it back to title-only (FN-7651 + removed the "Search in title only" button per user request). Rendered on both desktop + and mobile since this sidebar markup is shared (mobile layout is a CSS breakpoint of + the same DOM), and only within the Direct scope — Rooms already hides search/list + entirely. */}
@@ -2807,17 +2806,6 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout data-testid="chat-search-input" />
-
{/* Session list section */}
diff --git a/packages/dashboard/app/components/__tests__/ChatView.autosize.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.autosize.test.tsx index 4def138e6f..694987746d 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.autosize.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.autosize.test.tsx @@ -83,8 +83,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [sessionOne, sessionTwo], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/components/__tests__/ChatView.content-search.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.content-search.test.tsx index 9f4ad0422b..5747df9272 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.content-search.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.content-search.test.tsx @@ -1,18 +1,15 @@ /* -FNXC:ChatSearch 2026-07-07-00:00: -Covers the "Search in title only" toggle affordance: renders on desktop AND mobile chat -sidebars (shared DOM, CSS-breakpoint driven), toggling calls setSearchInTitleOnly, the toggle -does not leak into the Rooms scope, and matchedMessagePreview renders when content-mode drove -a session's inclusion. +FNXC:ChatSearch 2026-07-07-12:00: +Covers content search: no "Search in title only" toggle renders on desktop or mobile chat +sidebars (FN-7651 removed the affordance), and matchedMessagePreview still renders when +content-mode drove a session's inclusion — content search remains always-on. */ import { describe, it, expect, vi } from "vitest"; import { screen } from "@testing-library/react"; -import { userEvent } from "@testing-library/user-event"; import { ChatView } from "../ChatView"; import { renderWithAct, setupMockChat, - setupMockRooms, mockViewportMode, activeSessionFixture, installChatViewEnv, @@ -50,48 +47,28 @@ vi.mock("../../api", () => ({ installChatViewEnv(); -describe("ChatView content search toggle", () => { - it("renders the title-only toggle on the desktop sidebar and calls setSearchInTitleOnly on click", async () => { +describe("ChatView content search", () => { + it("does not render the title-only toggle on the desktop sidebar", async () => { mockViewportMode("desktop"); - const setSearchInTitleOnly = vi.fn(); - setupMockChat({ sessions: [], filteredSessions: [], searchInTitleOnly: false, setSearchInTitleOnly }); - - await renderWithAct(); - - const toggle = screen.getByTestId("chat-search-title-only-toggle"); - expect(toggle).toBeInTheDocument(); - expect(toggle).toHaveAttribute("aria-pressed", "false"); - - await userEvent.click(toggle); - expect(setSearchInTitleOnly).toHaveBeenCalledWith(true); - }); - - it("renders the title-only toggle on the mobile sidebar and calls setSearchInTitleOnly on click", async () => { - mockViewportMode("mobile"); - const setSearchInTitleOnly = vi.fn(); - setupMockChat({ sessions: [], filteredSessions: [], searchInTitleOnly: true, setSearchInTitleOnly }); - - await renderWithAct(); - - const toggle = screen.getByTestId("chat-search-title-only-toggle"); - expect(toggle).toBeInTheDocument(); - expect(toggle).toHaveAttribute("aria-pressed", "true"); - - await userEvent.click(toggle); - expect(setSearchInTitleOnly).toHaveBeenCalledWith(false); - }); - - it("does not render the toggle in Rooms scope", async () => { setupMockChat({ sessions: [], filteredSessions: [] }); - await renderWithAct(); - - await userEvent.click(screen.getByTestId("chat-sidebar-scope-rooms")); + await renderWithAct(); + expect(screen.getByTestId("chat-search-input")).toBeInTheDocument(); expect(screen.queryByTestId("chat-search-title-only-toggle")).toBeNull(); }); - it("shows matchedMessagePreview for a session included via content match", async () => { + it("does not render the title-only toggle on the mobile sidebar", async () => { + mockViewportMode("mobile"); + setupMockChat({ sessions: [], filteredSessions: [] }); + + await renderWithAct(); + + expect(screen.getByTestId("chat-search-input")).toBeInTheDocument(); + expect(screen.queryByTestId("chat-search-title-only-toggle")).toBeNull(); + }); + + it("shows matchedMessagePreview for a session included via content match, with no toggle present", async () => { const contentMatchedSession = { ...activeSessionFixture, id: "session-content-match", @@ -102,7 +79,6 @@ describe("ChatView content search toggle", () => { sessions: [contentMatchedSession], filteredSessions: [contentMatchedSession], searchQuery: "roadmap", - searchInTitleOnly: false, }); await renderWithAct(); @@ -110,5 +86,6 @@ describe("ChatView content search toggle", () => { expect(screen.getByTestId("chat-session-matched-preview-session-content-match")).toHaveTextContent( "quarterly roadmap discussion", ); + expect(screen.queryByTestId("chat-search-title-only-toggle")).toBeNull(); }); }); diff --git a/packages/dashboard/app/components/__tests__/ChatView.draft.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.draft.test.tsx index 355468f64c..6183c61dfb 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.draft.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.draft.test.tsx @@ -91,8 +91,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [sessionOne, sessionTwo], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/components/__tests__/ChatView.hash-mention.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.hash-mention.test.tsx index ab82bd17c0..d0ad0e01f4 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.hash-mention.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.hash-mention.test.tsx @@ -76,8 +76,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [activeSession], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx index be531ec401..9b7ed4a42c 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.mobile-render.test.tsx @@ -74,8 +74,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [], refreshSessions: vi.fn(), agentsMap: new Map([["agent-001", { id: "agent-001", name: "Alpha" }]]), diff --git a/packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx index ae96c8ff88..ed19525853 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.rooms.test.tsx @@ -83,8 +83,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [activeSession], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/components/__tests__/ChatView.scroll-to-top.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.scroll-to-top.test.tsx index 10525b8d75..8b46cced2f 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.scroll-to-top.test.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.scroll-to-top.test.tsx @@ -79,8 +79,6 @@ const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [activeSession], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/components/__tests__/ChatView.test-harness.tsx b/packages/dashboard/app/components/__tests__/ChatView.test-harness.tsx index adade20445..afaa7d28e7 100644 --- a/packages/dashboard/app/components/__tests__/ChatView.test-harness.tsx +++ b/packages/dashboard/app/components/__tests__/ChatView.test-harness.tsx @@ -74,8 +74,6 @@ export const defaultChatState: UseChatReturn = { hasMoreMessages: false, searchQuery: "", setSearchQuery: vi.fn(), - searchInTitleOnly: false, - setSearchInTitleOnly: vi.fn(), filteredSessions: [], refreshSessions: vi.fn(), agentsMap: new Map(), diff --git a/packages/dashboard/app/hooks/useChat.ts b/packages/dashboard/app/hooks/useChat.ts index f6472a0b9a..6b8238b803 100644 --- a/packages/dashboard/app/hooks/useChat.ts +++ b/packages/dashboard/app/hooks/useChat.ts @@ -118,13 +118,11 @@ export interface UseChatReturn { searchQuery: string; setSearchQuery: (query: string) => void; /** - * FNXC:ChatSearch 2026-07-07-00:00: - * When true, search matches only session title/agentId (the original client-side-only - * behavior). When false (default), search also matches message content via a debounced - * server round trip; matched sessions are unioned into `filteredSessions`. + * FNXC:ChatSearch 2026-07-07-12:00: + * Search always matches session title/agentId AND message content via a debounced server + * round trip; matched sessions are unioned into `filteredSessions`. There is no client toggle + * to restrict search back to title-only (FN-7651 removed the "Search in title only" button). */ - searchInTitleOnly: boolean; - setSearchInTitleOnly: (value: boolean) => void; filteredSessions: ChatSessionInfo[]; // Refresh @@ -352,14 +350,13 @@ export function useChat( // Search/filter const [searchQuery, setSearchQuery] = useState(""); /* - FNXC:ChatSearch 2026-07-07-00:00: - Default is content mode (searchInTitleOnly=false): the query matches title/agentId AND - message content. The toggle flips this to the pre-existing title/agentId-only behavior. + FNXC:ChatSearch 2026-07-07-12:00: + Content mode is always on: the query matches title/agentId AND message content. There is no + client toggle to restrict this back to title/agentId-only (FN-7651 removed the button). */ - const [searchInTitleOnly, setSearchInTitleOnly] = useState(false); const [contentMatchedPreviews, setContentMatchedPreviews] = useState>(new Map()); // Monotonic request counter: guards against an out-of-order/superseded debounced content - // search response overwriting a newer query's results (or a toggle-to-title-only reset). + // search response overwriting a newer query's results. const contentSearchRequestIdRef = useRef(0); // Pagination @@ -1307,17 +1304,17 @@ export function useChat( ); /* - FNXC:ChatSearch 2026-07-07-00:00: + FNXC:ChatSearch 2026-07-07-12:00: Content search requires a server round trip (message bodies are not fully loaded client-side), so it is debounced (300ms) and guarded against out-of-order responses via a - monotonic request id: a superseded query (typed-ahead, or a toggle back to title-only) - invalidates in-flight responses instead of letting a stale result flash in. Switching to - title-only or clearing the query resets `contentMatchedPreviews` synchronously so there is - no stale-result flash while the (now-irrelevant) debounced fetch is still pending/aborted. + monotonic request id: a superseded query (typed-ahead) invalidates in-flight responses + instead of letting a stale result flash in. Clearing the query resets + `contentMatchedPreviews` synchronously so there is no stale-result flash while the + (now-irrelevant) debounced fetch is still pending/aborted. */ const trimmedSearchQuery = searchQuery.trim(); useEffect(() => { - if (searchInTitleOnly || !trimmedSearchQuery) { + if (!trimmedSearchQuery) { contentSearchRequestIdRef.current++; setContentMatchedPreviews(new Map()); return; @@ -1346,10 +1343,10 @@ export function useChat( }, 300); return () => clearTimeout(timeoutId); - }, [trimmedSearchQuery, searchInTitleOnly, projectId]); + }, [trimmedSearchQuery, projectId]); // Filter sessions based on search query: title/agentId match always applies; content - // matches (from contentMatchedPreviews) are unioned in unless searchInTitleOnly is set. + // matches (from contentMatchedPreviews) are always unioned in. const filteredSessions = (() => { if (!trimmedSearchQuery) return sessions; @@ -1360,7 +1357,7 @@ export function useChat( s.agentId.toLowerCase().includes(lowerQuery), ); - if (searchInTitleOnly || contentMatchedPreviews.size === 0) { + if (contentMatchedPreviews.size === 0) { return titleMatched; } @@ -1639,8 +1636,6 @@ export function useChat( hasMoreMessages, searchQuery, setSearchQuery, - searchInTitleOnly, - setSearchInTitleOnly, filteredSessions, refreshSessions, agentsMap, diff --git a/packages/i18n/locales/en/app.json b/packages/i18n/locales/en/app.json index 4a8bc34a97..7e73f1b614 100644 --- a/packages/i18n/locales/en/app.json +++ b/packages/i18n/locales/en/app.json @@ -1363,7 +1363,6 @@ "scopeRooms": "Rooms", "scrollMessageToTop": "Scroll message to top", "searchConversations": "Search conversations...", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "Select agent for new chat", "selectAgentPlaceholder": "Select an agent to start chatting", "selectModel": "Select a model", diff --git a/packages/i18n/locales/es/app.json b/packages/i18n/locales/es/app.json index eea2a064c0..efe2d727e3 100644 --- a/packages/i18n/locales/es/app.json +++ b/packages/i18n/locales/es/app.json @@ -1352,7 +1352,6 @@ "scopeRooms": "Canales", "scrollMessageToTop": "Desplazar mensaje al inicio", "searchConversations": "Buscar conversaciones...", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "Seleccionar agente para el nuevo chat", "selectAgentPlaceholder": "Selecciona un agente para empezar a chatear", "selectModel": "Seleccionar un modelo", diff --git a/packages/i18n/locales/fr/app.json b/packages/i18n/locales/fr/app.json index 016a618985..6f22765085 100644 --- a/packages/i18n/locales/fr/app.json +++ b/packages/i18n/locales/fr/app.json @@ -1352,7 +1352,6 @@ "scopeRooms": "Salons", "scrollMessageToTop": "Faire défiler le message vers le haut", "searchConversations": "Rechercher des conversations…", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "Sélectionner un agent pour le nouveau chat", "selectAgentPlaceholder": "Sélectionnez un agent pour commencer à discuter", "selectModel": "Choisir un modèle", diff --git a/packages/i18n/locales/ko/app.json b/packages/i18n/locales/ko/app.json index 2768165bcb..e54dc1b9f0 100644 --- a/packages/i18n/locales/ko/app.json +++ b/packages/i18n/locales/ko/app.json @@ -1352,7 +1352,6 @@ "scopeRooms": "방", "scrollMessageToTop": "메시지를 맨 위로 스크롤", "searchConversations": "대화 검색...", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "새 채팅을 위한 에이전트 선택", "selectAgentPlaceholder": "채팅을 시작할 에이전트를 선택하세요", "selectModel": "모델 선택", diff --git a/packages/i18n/locales/zh-CN/app.json b/packages/i18n/locales/zh-CN/app.json index a54adc1b12..93122c0e98 100644 --- a/packages/i18n/locales/zh-CN/app.json +++ b/packages/i18n/locales/zh-CN/app.json @@ -1352,7 +1352,6 @@ "scopeRooms": "频道", "scrollMessageToTop": "将消息滚动到顶部", "searchConversations": "搜索对话...", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "为新聊天选择代理", "selectAgentPlaceholder": "选择代理开始聊天", "selectModel": "选择模型", diff --git a/packages/i18n/locales/zh-TW/app.json b/packages/i18n/locales/zh-TW/app.json index dd4808c5b6..01607059af 100644 --- a/packages/i18n/locales/zh-TW/app.json +++ b/packages/i18n/locales/zh-TW/app.json @@ -1352,7 +1352,6 @@ "scopeRooms": "頻道", "scrollMessageToTop": "將訊息捲動至頂部", "searchConversations": "搜尋對話...", - "searchInTitleOnly": "Search in title only", "selectAgentForNewChat": "為新聊天選擇代理", "selectAgentPlaceholder": "選擇代理以開始聊天", "selectModel": "選擇模型",