From 19055c6c0ebd5ae6eecc4af2cd0fd6e3a7a79475 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 10 Jul 2026 23:31:48 -0700 Subject: [PATCH] feat(settings): simplify advanced controls --- .changeset/calm-settings-navigation.md | 7 ++ .../settings-advanced/desktop-advanced.png | 3 + .../settings-advanced/desktop-basic.png | 3 + .../settings-advanced/mobile-advanced.png | 3 + .../settings-advanced/mobile-basic.png | 3 + .../app/components/SettingsModal.css | 26 +++++++ .../app/components/SettingsModal.tsx | 78 ++++++++++++++++++- .../__tests__/SettingsModal.general.test.tsx | 31 +++++++- 8 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 .changeset/calm-settings-navigation.md create mode 100644 docs/screenshots/settings-advanced/desktop-advanced.png create mode 100644 docs/screenshots/settings-advanced/desktop-basic.png create mode 100644 docs/screenshots/settings-advanced/mobile-advanced.png create mode 100644 docs/screenshots/settings-advanced/mobile-basic.png diff --git a/.changeset/calm-settings-navigation.md b/.changeset/calm-settings-navigation.md new file mode 100644 index 0000000000..c2e943425c --- /dev/null +++ b/.changeset/calm-settings-navigation.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Add a persistent Advanced settings toggle that keeps uncommon Settings sections hidden by default. +category: feature +dev: The browser-local disclosure applies to desktop, mobile, and Settings search without changing saved settings. diff --git a/docs/screenshots/settings-advanced/desktop-advanced.png b/docs/screenshots/settings-advanced/desktop-advanced.png new file mode 100644 index 0000000000..121a43d4c6 --- /dev/null +++ b/docs/screenshots/settings-advanced/desktop-advanced.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:674d24e02998d9ccdf85ffbc2dc8dc4fa1c070964ef4096b33aed8f8e3d56459 +size 189060 diff --git a/docs/screenshots/settings-advanced/desktop-basic.png b/docs/screenshots/settings-advanced/desktop-basic.png new file mode 100644 index 0000000000..dac9349202 --- /dev/null +++ b/docs/screenshots/settings-advanced/desktop-basic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b2d0d6d3bddc2f06f178658b6162a088f73154c86b1f3d515728ac0f8169673 +size 182222 diff --git a/docs/screenshots/settings-advanced/mobile-advanced.png b/docs/screenshots/settings-advanced/mobile-advanced.png new file mode 100644 index 0000000000..7c27793aa7 --- /dev/null +++ b/docs/screenshots/settings-advanced/mobile-advanced.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:429955254d3ee2cac2e0b40016c158e00d37034488b69b2cb0d4411231c3354f +size 258818 diff --git a/docs/screenshots/settings-advanced/mobile-basic.png b/docs/screenshots/settings-advanced/mobile-basic.png new file mode 100644 index 0000000000..189e07c840 --- /dev/null +++ b/docs/screenshots/settings-advanced/mobile-basic.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b9441fcaa82f557113c685622b792f1a8c898971b2709f256ba6e5f9c1dd945 +size 258732 diff --git a/packages/dashboard/app/components/SettingsModal.css b/packages/dashboard/app/components/SettingsModal.css index ecedddbd42..f92152d2f9 100644 --- a/packages/dashboard/app/components/SettingsModal.css +++ b/packages/dashboard/app/components/SettingsModal.css @@ -641,6 +641,27 @@ Fix the invariant for BOTH presentations (standalone modal + embedded SettingsVi border-bottom: var(--btn-border-width) solid var(--border); } +/* +FNXC:SettingsSimplification 2026-07-10-23:24: +The Advanced settings preference is a navigation-level disclosure, so keep it visually adjacent to section search/picking in both desktop and mobile layouts and retain a full-row click target. +*/ +.settings-advanced-toggle { + display: flex; + align-items: center; + gap: var(--space-sm); + padding: var(--space-sm); + border-bottom: var(--btn-border-width) solid var(--border); + color: var(--text); + font-size: 0.8rem; + font-weight: 600; + cursor: pointer; +} + +.settings-advanced-toggle input { + margin: 0; + cursor: pointer; +} + /* FNXC:Settings 2026-07-09-12:00: the row wraps label + input + results; on desktop it always renders (see .settings-search-toggle below, hidden here). On mobile the inline section-picker toggle controls whether this wrapper mounts below the picker, avoiding an empty padded shell above the picker while collapsed. */ .settings-search-row { display: flex; @@ -2282,6 +2303,11 @@ The header row wraps so the badge drops below the heading on narrow widths inste padding: var(--space-sm) var(--space-md) var(--space-sm); } + .settings-advanced-toggle { + padding: 0 var(--space-md) var(--space-sm); + border-bottom: none; + } + /* FNXC:Settings 2026-07-09-12:00: mobile-only collapse-by-default toggle sits in the section picker control row so search and section navigation share one horizontal affordance row. The row starts collapsed and expands to the full search row below the picker on tap. */ .settings-search-toggle { --settings-search-toggle-touch-target: calc(var(--space-lg) + var(--space-lg) + var(--space-xs)); diff --git a/packages/dashboard/app/components/SettingsModal.tsx b/packages/dashboard/app/components/SettingsModal.tsx index 4a66330d81..41ddfb9c20 100644 --- a/packages/dashboard/app/components/SettingsModal.tsx +++ b/packages/dashboard/app/components/SettingsModal.tsx @@ -238,6 +238,52 @@ type SettingsSection = { const MOBILE_SETTINGS_MEDIA_QUERY = "(max-width: 768px)"; const DEFAULT_MEMORY_EDITOR_PATH = ".fusion/memory/DREAMS.md"; +const ADVANCED_SETTINGS_STORAGE_KEY = "fusion:settings:show-advanced"; + +/* +FNXC:SettingsSimplification 2026-07-10-23:24: +Settings opens in a focused mode that omits specialist integration, runtime, diagnostics, and infrastructure sections. The Advanced settings switch restores every section, applies consistently to desktop navigation, mobile navigation, and search, and persists only as a browser-local display preference so it never changes or exports project settings. +*/ +const ADVANCED_SETTINGS_SECTION_IDS = new Set([ + "node-sync", + "global-mcp", + "cli-agents", + "research-global", + "remote", + "experimental", + "hermes-runtime", + "openclaw-runtime", + "paperclip-runtime", + "scheduled-evals", + "node-routing", + "agent-permissions", + "memory", + "backups", + "research-project", + "secrets", + "mcp", + "prompts", + "plugins", +]); + +function readAdvancedSettingsPreference(): boolean { + try { + return localStorage.getItem(ADVANCED_SETTINGS_STORAGE_KEY) === "true"; + } catch { + return false; + } +} + +function removeEmptySettingsGroups(sections: SettingsSection[]): SettingsSection[] { + return sections.filter((section, index) => { + if (!section.isGroupHeader) return true; + for (const candidate of sections.slice(index + 1)) { + if (candidate.isGroupHeader) return false; + return true; + } + return false; + }); +} function normalizeSettingsSearchText(value: string): string { return value.trim().toLocaleLowerCase(); @@ -978,6 +1024,22 @@ export function SettingsModal({ : false), ); const [settingsSearchQuery, setSettingsSearchQuery] = useState(""); + const [showAdvancedSettings, setShowAdvancedSettings] = useState(() => { + const requestedSection = initialSection === "pi-extensions" ? "plugins" : initialSection; + /* + FNXC:SettingsSimplification 2026-07-10-23:24: + Product links that intentionally open a specific advanced section must remain usable. Reveal advanced navigation for that Settings session, but do not persist the implicit reveal; only a direct user toggle changes the local-storage preference. + */ + return readAdvancedSettingsPreference() || (requestedSection !== undefined && ADVANCED_SETTINGS_SECTION_IDS.has(requestedSection)); + }); + const handleAdvancedSettingsChange = useCallback((enabled: boolean) => { + setShowAdvancedSettings(enabled); + try { + localStorage.setItem(ADVANCED_SETTINGS_STORAGE_KEY, String(enabled)); + } catch { + // Storage can be unavailable in private/locked-down browser contexts; the in-session preference still works. + } + }, []); /* * FNXC:Settings 2026-07-09-00:00: * Mobile Settings previously always rendered the `.settings-search` row (label + input + result @@ -1042,7 +1104,11 @@ export function SettingsModal({ const experimentalFeatures = form.experimentalFeatures ?? {}; const researchViewEnabled = isExperimentalFeatureEnabled(experimentalFeatures, "researchView"); const evalsViewEnabled = isExperimentalFeatureEnabled(experimentalFeatures, "evalsView"); - const visibleSections = useMemo(() => SETTINGS_SECTIONS.filter((section) => { + const visibleSections = useMemo(() => removeEmptySettingsGroups(SETTINGS_SECTIONS.filter((section) => { + if (!showAdvancedSettings && ADVANCED_SETTINGS_SECTION_IDS.has(section.id)) { + return false; + } + if (section.id === "research-global" || section.id === "research-project") { return researchViewEnabled; } @@ -1052,7 +1118,7 @@ export function SettingsModal({ } return true; - }), [researchViewEnabled, evalsViewEnabled]); + })), [researchViewEnabled, evalsViewEnabled, showAdvancedSettings]); const firstVisibleSectionId = visibleSections.some((section) => section.id === DEFAULT_SETTINGS_SECTION) ? DEFAULT_SETTINGS_SECTION : resolveFirstSelectableSettingsSection(visibleSections, firstNonHeaderSection?.id ?? "general"); @@ -3740,6 +3806,14 @@ export function SettingsModal({ )} + {settingsSearchRowVisible && (
diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx index d2dbade5a8..1737850431 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx @@ -228,6 +228,31 @@ describe("SettingsModal", () => { expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument(); }); + /* + FNXC:SettingsSimplification 2026-07-10-23:24: + Advanced settings must default off for a new browser, hide specialist sections from every navigation surface, and restore the browser-local preference without mutating Fusion settings. + */ + it("hides advanced sections by default and persists the disclosure preference in local storage", async () => { + const firstRender = renderModal({ initialSection: "authentication" }); + await waitForSettingsModalReady(); + + const toggle = screen.getByRole("checkbox", { name: "Advanced settings" }); + expect(toggle).not.toBeChecked(); + expect(screen.queryByRole("button", { name: /^Node Sync$/ })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /^Experimental Features$/ })).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^Appearance$/ })).toBeInTheDocument(); + + await settingsModalUser.click(toggle); + expect(localStorage.getItem("fusion:settings:show-advanced")).toBe("true"); + expect(screen.getByRole("button", { name: /^Node Sync$/ })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^Experimental Features$/ })).toBeInTheDocument(); + + firstRender.unmount(); + renderModal({ initialSection: "authentication" }); + await waitForSettingsModalReady(); + expect(screen.getByRole("checkbox", { name: "Advanced settings" })).toBeChecked(); + }); + it("honors an explicit initialSection override", async () => { renderModal({ initialSection: "authentication" }); await waitForSettingsModalReady(); @@ -239,6 +264,7 @@ describe("SettingsModal", () => { it("filters settings navigation by section and setting-level keywords without exposing hidden sections", async () => { renderModal(); await waitForSettingsModalReady(); + await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" })); // FN-7713: this file mocks useViewportMode to "mobile", so the search row starts collapsed // behind the toggle — expand it before interacting with the search input. @@ -277,6 +303,7 @@ describe("SettingsModal", () => { it("keeps duplicate global and project labels searchable while preserving no-results clearing", async () => { renderModal(); await waitForSettingsModalReady(); + await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" })); // FN-7713: search row is collapsed by default under the "mobile" viewport mock — expand it first. await settingsModalUser.click(screen.getByLabelText("Show search")); @@ -388,6 +415,7 @@ describe("SettingsModal", () => { it("shows a Secrets entry in the settings nav", async () => { renderModal(); await waitForSettingsModalReady(); + await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" })); expect(screen.getByRole("button", { name: "Secrets" })).toBeInTheDocument(); expect(screen.queryByRole("link", { name: "Manage secrets" })).not.toBeInTheDocument(); @@ -396,6 +424,7 @@ describe("SettingsModal", () => { it("renders the SecretsView when the Secrets section is selected", async () => { renderModal(); await waitForSettingsModalReady(); + await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" })); await settingsModalUser.click(screen.getByRole("button", { name: "Secrets" })); @@ -568,6 +597,7 @@ describe("SettingsModal", () => { it("enables memory backend status hook only when Memory section is active", async () => { renderModal(); await waitForSettingsModalReady(); + await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" })); expect(mockUseMemoryBackendStatus).toHaveBeenCalled(); const initialCallHasDisabled = mockUseMemoryBackendStatus.mock.calls.some( @@ -1631,4 +1661,3 @@ describe("SettingsModal", () => { }); }); }); -