FN-7651: remove chat "Search in title only" toggle

Simplifies chat search UX by always matching both title and message content, removing the now-unneeded title-only toggle button and state.

- Removed the "Search in title only" toggle button and its CSS from ChatView
- Dropped searchInTitleOnly/setSearchInTitleOnly state and logic from useChat; content-search query params are now always-on
- Updated ChatView tests to drop title-only toggle interactions and assertions
- Removed the title-only-search i18n string across all locales
- Updated dashboard-guide.md docs to reflect the simplified search behavior
- Added a patch changeset documenting the removal

Files changed:
 .../fn-7651-remove-chat-title-only-toggle.md       |  7 +++
 docs/dashboard-guide.md                            |  4 +-
 packages/dashboard/app/components/ChatView.css     | 18 --------
 packages/dashboard/app/components/ChatView.tsx     | 26 +++--------
 .../__tests__/ChatView.autosize.test.tsx           |  2 -
 .../__tests__/ChatView.content-search.test.tsx     | 51 ++++++----------------
 .../components/__tests__/ChatView.draft.test.tsx   |  2 -
 .../__tests__/ChatView.hash-mention.test.tsx       |  2 -
 .../__tests__/ChatView.mobile-render.test.tsx      |  2 -
 .../components/__tests__/ChatView.rooms.test.tsx   |  2 -
 .../__tests__/ChatView.scroll-to-top.test.tsx      |  2 -
 .../components/__tests__/ChatView.test-harness.tsx |  2 -
 packages/dashboard/app/hooks/useChat.ts            | 39 ++++++++---------
 packages/i18n/locales/en/app.json                  |  1 -
 packages/i18n/locales/es/app.json                  |  1 -
 packages/i18n/locales/zh-CN/app.json               |  1 -
 packages/i18n/locales/zh-TW/app.json               |  1 -
 packages/i18n/locales/ko/app.json                  |  1 -
 packages/i18n/locales/fr/app.json                  |  1 -
 19 files changed, 47 insertions(+), 118 deletions(-)

Fusion-Task-Id: FN-7651

Fusion-Task-Lineage: 5a31825c-8e6e-441f-800d-6053f5f844ba

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-07 23:15:53 -07:00
parent ac719d1203
commit efabdd6f04
19 changed files with 53 additions and 124 deletions

View File

@@ -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.

View File

@@ -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.
<!-- FNXC:TaskDetailPlannerChat 2026-07-07-10:15: Document Planner Chat edit-and-resend and the steering/refinement side-effect decision on discard. -->
- 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.
<!-- FNXC:ChatSearch 2026-07-07-00:00: Document content search + the title-only toggle so users know search now covers message bodies by default, not just titles. -->
- 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**).
<!-- FNXC:ChatSearch 2026-07-07-12:00: Content search is always on (FN-7651 removed the "Search in title only" toggle per user request); document the always-on behavior instead of a switchable toggle. -->
- 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)

View File

@@ -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;

View File

@@ -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.
*/}
<div className="chat-sidebar-search-container">
<div className="chat-sidebar-search-wrapper">
@@ -2807,17 +2806,6 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
data-testid="chat-search-input"
/>
</div>
<button
type="button"
className="btn btn-sm chat-search-title-only-toggle"
onClick={() => setSearchInTitleOnly(!searchInTitleOnly)}
aria-pressed={searchInTitleOnly}
aria-label={t("chat.searchInTitleOnly", "Search in title only")}
title={t("chat.searchInTitleOnly", "Search in title only")}
data-testid="chat-search-title-only-toggle"
>
{t("chat.searchInTitleOnly", "Search in title only")}
</button>
</div>
{/* Session list section */}
<div className="chat-session-list chat-sidebar-list">

View File

@@ -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(),

View File

@@ -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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
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(<ChatView projectId="proj-123" addToast={vi.fn()} experimentalFeatures={{ chatRooms: true }} />);
await userEvent.click(screen.getByTestId("chat-sidebar-scope-rooms"));
await renderWithAct(<ChatView projectId="proj-123" addToast={vi.fn()} />);
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
@@ -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();
});
});

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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" }]]),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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(),

View File

@@ -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<Map<string, string>>(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,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",

View File

@@ -1352,7 +1352,6 @@
"scopeRooms": "방",
"scrollMessageToTop": "메시지를 맨 위로 스크롤",
"searchConversations": "대화 검색...",
"searchInTitleOnly": "Search in title only",
"selectAgentForNewChat": "새 채팅을 위한 에이전트 선택",
"selectAgentPlaceholder": "채팅을 시작할 에이전트를 선택하세요",
"selectModel": "모델 선택",

View File

@@ -1352,7 +1352,6 @@
"scopeRooms": "频道",
"scrollMessageToTop": "将消息滚动到顶部",
"searchConversations": "搜索对话...",
"searchInTitleOnly": "Search in title only",
"selectAgentForNewChat": "为新聊天选择代理",
"selectAgentPlaceholder": "选择代理开始聊天",
"selectModel": "选择模型",

View File

@@ -1352,7 +1352,6 @@
"scopeRooms": "頻道",
"scrollMessageToTop": "將訊息捲動至頂部",
"searchConversations": "搜尋對話...",
"searchInTitleOnly": "Search in title only",
"selectAgentForNewChat": "為新聊天選擇代理",
"selectAgentPlaceholder": "選擇代理以開始聊天",
"selectModel": "選擇模型",