FN-7942: index Project Models chat-default settings in Settings search

Extends the shared Settings search index so queries like "chat" surface the Project Models section's Direct-chat default fields, which previously had no matching searchable terms.

- Add chat-related searchableText keywords (chat, new chat, chat default, chat model, chat agent, etc.) to the project-models SETTINGS_SECTIONS entry
- Add searchableKeys covering the i18n chat-default labels (chatHeading, chatDescription, chatNewSessionMode*, chatDefaultKind/Model/Agent) so translated labels are also indexed
- Export SettingsSection type plus normalizeSettingsSearchText/sectionMatchesSettingsSearch/filterSettingsSectionsForSearch/SETTINGS_SECTIONS from SettingsModal.tsx for direct unit testing
- Add SettingsModal.search.test.ts covering chat-query matches and a negative case (unrelated Remote Access term does not match)
- Add changeset fn-7942-settings-search-chat.md (patch, fix category)

Files changed:
 .changeset/fn-7942-settings-search-chat.md         |  7 +++
 .../dashboard/app/components/SettingsModal.tsx     | 50 +++++++++++++++++++---
 .../__tests__/SettingsModal.search.test.ts         | 41 ++++++++++++++++++
 3 files changed, 92 insertions(+), 6 deletions(-)

Fusion-Task-Id: FN-7942

Fusion-Task-Lineage: fa044908-7ada-4334-a789-6d2bf08afdb8

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-13 07:44:12 -07:00
parent 281bb05161
commit cf1b33bf5e
3 changed files with 92 additions and 6 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Settings search now surfaces Project Models Chat default settings when searching for chat.
category: fix
dev: Adds chat-default searchableText/searchableKeys to the project-models entry in SETTINGS_SECTIONS (SettingsModal.tsx); fixes FN-7907 search-index drift.

View File

@@ -225,7 +225,7 @@ function useGitHubStarCount(): number | null {
* 2. Add a corresponding case in renderSectionFields() * 2. Add a corresponding case in renderSectionFields()
*/ */
/** Section entry type with optional icon */ /** Section entry type with optional icon */
type SettingsSection = { export type SettingsSection = {
id: string; id: string;
label: string; label: string;
labelKey: string; labelKey: string;
@@ -303,11 +303,11 @@ function removeEmptySettingsGroups(sections: SettingsSection[]): SettingsSection
}); });
} }
function normalizeSettingsSearchText(value: string): string { export function normalizeSettingsSearchText(value: string): string {
return value.trim().toLocaleLowerCase(); return value.trim().toLocaleLowerCase();
} }
function sectionMatchesSettingsSearch( export function sectionMatchesSettingsSearch(
section: SettingsSection, section: SettingsSection,
query: string, query: string,
label: string, label: string,
@@ -326,7 +326,7 @@ function sectionMatchesSettingsSearch(
.some((candidate) => candidate.includes(query)); .some((candidate) => candidate.includes(query));
} }
function filterSettingsSectionsForSearch( export function filterSettingsSectionsForSearch(
sections: SettingsSection[], sections: SettingsSection[],
query: string, query: string,
translateLabel: (section: SettingsSection) => string, translateLabel: (section: SettingsSection) => string,
@@ -413,7 +413,7 @@ function resolveMaxAutoMergeRetriesForSettingsForm(settings?: { maxAutoMergeRetr
return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3; return Number.isFinite(configured) && configured > 0 ? Math.floor(configured) : 3;
} }
const SETTINGS_SECTIONS: SettingsSection[] = [ export const SETTINGS_SECTIONS: SettingsSection[] = [
// Global group (shared across all Fusion projects) // Global group (shared across all Fusion projects)
{ id: "__global_header", label: "Global", labelKey: "settings.nav.globalHeader", scope: undefined, isGroupHeader: true }, { id: "__global_header", label: "Global", labelKey: "settings.nav.globalHeader", scope: undefined, isGroupHeader: true },
{ id: "global-general", label: "General", labelKey: "settings.nav.globalGeneral", scope: "global", searchableText: ["global defaults", "modal outside dismiss", "agent logs", "persist tool output", "thinking logs", "GitLab instance URL", "global tracking repo"] }, { id: "global-general", label: "General", labelKey: "settings.nav.globalGeneral", scope: "global", searchableText: ["global defaults", "modal outside dismiss", "agent logs", "persist tool output", "thinking logs", "GitLab instance URL", "global tracking repo"] },
@@ -484,7 +484,45 @@ const SETTINGS_SECTIONS: SettingsSection[] = [
{ id: "memory", label: "Memory", labelKey: "settings.nav.memory", scope: "project", searchableText: ["memory backend", "Dreams", "long-term memory", "qmd", "memory file", "retrieval"] }, { id: "memory", label: "Memory", labelKey: "settings.nav.memory", scope: "project", searchableText: ["memory backend", "Dreams", "long-term memory", "qmd", "memory file", "retrieval"] },
{ id: "backups", label: "Backups", labelKey: "settings.nav.backups", scope: "project", searchableText: ["backup", "restore", "settings export", "settings import"] }, { id: "backups", label: "Backups", labelKey: "settings.nav.backups", scope: "project", searchableText: ["backup", "restore", "settings export", "settings import"] },
{ id: "research-project", label: "Research", labelKey: "settings.nav.researchProject", scope: "project", searchableText: ["project research", "research runs", "citations", "search limits", "fetch synthesis"] }, { id: "research-project", label: "Research", labelKey: "settings.nav.researchProject", scope: "project", searchableText: ["project research", "research runs", "citations", "search limits", "fetch synthesis"] },
{ id: "project-models", label: "Project Models", labelKey: "settings.nav.projectModels", scope: "project", searchableText: ["default provider", "default model", "workflow model lanes", "Plan/Triage", "Executor", "Reviewer", "summarization model"] }, /**
* FNXC:SettingsNavigation 2026-07-13-00:00:
* Project Models owns the FN-7907 Direct-chat default settings. Its shared Settings search index must advertise chat-default terms and i18n labels so desktop nav, the mobile section picker, and filtered search all surface this section when operators search for Chat defaults.
*/
{
id: "project-models",
label: "Project Models",
labelKey: "settings.nav.projectModels",
scope: "project",
searchableText: [
"default provider",
"default model",
"workflow model lanes",
"Plan/Triage",
"Executor",
"Reviewer",
"summarization model",
"chat",
"new chat",
"new chat behavior",
"chat default",
"chat default model",
"chat default agent",
"chat model",
"chat agent",
"prompt for model",
"always use default",
],
searchableKeys: [
"settings.projectModels.chatHeading",
"settings.projectModels.chatDescription",
"settings.projectModels.chatNewSessionMode",
"settings.projectModels.chatNewSessionModePrompt",
"settings.projectModels.chatNewSessionModeAlwaysDefault",
"settings.projectModels.chatDefaultKind",
"settings.projectModels.chatDefaultModel",
"settings.projectModels.chatDefaultAgent",
],
},
{ id: "secrets", label: "Secrets", labelKey: "settings.nav.secrets", scope: "project", searchableText: ["secrets", "secret storage", "environment", "credentials"] }, { id: "secrets", label: "Secrets", labelKey: "settings.nav.secrets", scope: "project", searchableText: ["secrets", "secret storage", "environment", "credentials"] },
{ id: "mcp", label: "MCP Servers", labelKey: "settings.nav.mcp", scope: "project", searchableText: ["project MCP servers", "workspace MCP", "project tool servers", "mcp config"] }, { id: "mcp", label: "MCP Servers", labelKey: "settings.nav.mcp", scope: "project", searchableText: ["project MCP servers", "workspace MCP", "project tool servers", "mcp config"] },
{ id: "prompts", label: "Prompts", labelKey: "settings.nav.prompts", scope: "project", searchableText: ["prompt instructions", "PR title prompt", "PR description prompt", "custom prompts"] }, { id: "prompts", label: "Prompts", labelKey: "settings.nav.prompts", scope: "project", searchableText: ["prompt instructions", "PR title prompt", "PR description prompt", "custom prompts"] },

View File

@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import {
filterSettingsSectionsForSearch,
normalizeSettingsSearchText,
SETTINGS_SECTIONS,
type SettingsSection,
} from "../SettingsModal";
const searchTranslations: Record<string, string> = {
"settings.projectModels.chatHeading": "Chat",
"settings.projectModels.chatDescription": "Choose the default target for new Direct chats and whether New Chat should prompt or immediately use that default.",
"settings.projectModels.chatNewSessionMode": "New Chat behavior",
"settings.projectModels.chatNewSessionModePrompt": "Prompt for model each time",
"settings.projectModels.chatNewSessionModeAlwaysDefault": "Always use configured default",
"settings.projectModels.chatDefaultKind": "Chat default target",
"settings.projectModels.chatDefaultModel": "Chat Default Model",
"settings.projectModels.chatDefaultAgent": "Chat Default Agent",
};
function searchSettingsSectionIds(query: string): string[] {
return filterSettingsSectionsForSearch(
SETTINGS_SECTIONS,
normalizeSettingsSearchText(query),
(section: SettingsSection) => section.label,
(key: string) => searchTranslations[key] ?? key,
).map((section) => section.id);
}
describe("SettingsModal Settings search index", () => {
it.each(["chat", "new chat", "chat model", "chat default agent"])(
"surfaces Project Models for the chat-default query %s",
(query) => {
expect(searchSettingsSectionIds(query)).toContain("project-models");
},
);
it("does not surface Project Models for unrelated Remote Access terms", () => {
expect(searchSettingsSectionIds("cloudflared")).not.toContain("project-models");
});
});