From f111a40c7217aae80cfc1bc7c23627cb144647f1 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 16 Jul 2026 13:55:37 -0700 Subject: [PATCH] FN-8130: default Settings to Appearance Open Settings on the always-visible Appearance section by default. - Change the Settings modal default and unavailable-section fallback to Appearance. - Cover desktop and mobile default selection and fallback behavior. - Add a patch changeset for the corrected landing section. Files changed: .changeset/default-settings-appearance.md | 7 ++++++ .../dashboard/app/components/SettingsModal.tsx | 16 ++++++++------ .../__tests__/SettingsModal.general.test.tsx | 15 +++++++------ .../SettingsModal.remote-notifications.test.tsx | 4 ++-- .../SettingsModal.scheduling-merge.test.tsx | 11 ++++++---- .../components/__tests__/settings-mobile.test.tsx | 25 ++++++++++++++++------ 6 files changed, 53 insertions(+), 25 deletions(-) Fusion-Task-Id: FN-8130 Fusion-Task-Lineage: de9e31d8-98e5-4a02-8135-c5a397f7b8ad Co-authored-by: Fusion (runfusion.ai) --- .changeset/default-settings-appearance.md | 7 ++++++ .../app/components/SettingsModal.tsx | 16 +++++++----- .../__tests__/SettingsModal.general.test.tsx | 15 +++++------ ...ettingsModal.remote-notifications.test.tsx | 4 +-- .../SettingsModal.scheduling-merge.test.tsx | 11 +++++--- .../__tests__/settings-mobile.test.tsx | 25 ++++++++++++++----- 6 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 .changeset/default-settings-appearance.md diff --git a/.changeset/default-settings-appearance.md b/.changeset/default-settings-appearance.md new file mode 100644 index 0000000000..433053af52 --- /dev/null +++ b/.changeset/default-settings-appearance.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Settings now opens on the Appearance section by default. +category: fix +dev: Sets SettingsModal DEFAULT_SETTINGS_SECTION to appearance. diff --git a/packages/dashboard/app/components/SettingsModal.tsx b/packages/dashboard/app/components/SettingsModal.tsx index eeb39db7d8..da9f6131a8 100644 --- a/packages/dashboard/app/components/SettingsModal.tsx +++ b/packages/dashboard/app/components/SettingsModal.tsx @@ -496,9 +496,11 @@ export const SETTINGS_SECTIONS: SettingsSection[] = [ { id: "__ai_header", label: "AI & Models", labelKey: "settings.nav.aiHeader", scope: undefined, isGroupHeader: true }, /* FNXC:SettingsNavigation 2026-07-16-01:30: - Authentication leads the AI & Models group, and Settings still opens on it. - It is a provider-credentials screen, so it belongs with the model settings it gates rather than under Integrations (where it sat among MCP/Plugins/runtimes) or floating above the groups as a special case — connecting a provider and choosing its models are one task, done in that order. + Authentication leads the AI & Models group. It is a provider-credentials screen, so it belongs with the model settings it gates rather than under Integrations (where it sat among MCP/Plugins/runtimes) or floating above the groups as a special case — connecting a provider and choosing its models are one task, done in that order. First within the group because nothing else in AI & Models can be configured until it is done: with no provider connected there are no models to pick. + + FNXC:SettingsNavigation 2026-07-16-13:40: + FN-8130 changes the Settings landing surface from Authentication to Appearance. Authentication remains first within its own AI & Models group, but the always-visible global Preferences section is the default instead. */ { id: "authentication", label: "Authentication", labelKey: "settings.nav.authentication", scope: undefined, icon: Globe, searchableText: ["login", "OAuth", "API key", "custom providers", "Anthropic", "OpenAI", "provider credentials"] }, { id: "global-models", label: "Models · Global", labelKey: "settings.nav.globalModels", scope: "global", searchableText: ["global models", "model presets", "favorite providers", "model pricing overrides", "LiteLLM pricing", "token pricing", "translate", "translation model", "import translation model", "import auto-translation model"] }, @@ -781,10 +783,12 @@ export type SectionId = SettingsSection["id"] | LegacySectionId; /* FNXC:SettingsNavigation 2026-07-16-01:00: -Settings opens on Authentication. It is the section an operator actually needs first — nothing else in the product works until a provider is connected, and the dashboard's own empty state points here ("Set one up in Settings → AI Setup"). The previous landing section was "General · Global", a page of app preferences (and, until this change, the `fn` binary panel) that nobody opens Settings to find. -It is also always visible: Authentication is not behind the Advanced switch, so the default landing section cannot be one the operator has hidden. +Authentication was the previous Settings landing section because a provider connection gates model configuration. It remains first within AI & Models, but no longer determines the modal default. + +FNXC:SettingsNavigation 2026-07-16-13:40: +FN-8130 requires Settings to open on Appearance, the global Preferences section, when no explicit initialSection is supplied. Appearance is always visible — it is not behind the Advanced switch — so the default can never land on a section hidden from the operator's navigation. */ -const DEFAULT_SETTINGS_SECTION: SectionId = "authentication"; +const DEFAULT_SETTINGS_SECTION: SectionId = "appearance"; type PluginsSubsectionId = "fusion-plugins" | "pi-extensions"; @@ -796,7 +800,7 @@ interface SettingsModalProps { onClose: () => void; addToast: (message: string, type?: ToastType) => void; projectId?: string; - /** Optional section to show when the modal first opens. Defaults to the global General section. */ + /** Optional section to show when the modal first opens. Defaults to the global Appearance section. */ initialSection?: SectionId; /** Current theme mode */ themeMode?: ThemeMode; diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx index a5d984851d..f51bd894b2 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.general.test.tsx @@ -285,10 +285,12 @@ describe("SettingsModal", () => { /* FNXC:SettingsNavigation 2026-07-16-01:10: - Settings opens on Authentication, not "General · Global". Nothing else in the product works until a provider is connected — the dashboard's own empty state sends operators to Settings for exactly this — whereas the old landing section was app preferences nobody opens Settings to find. - Authentication is also not behind the Advanced switch, so the landing section can never be one the operator has hidden; that is asserted here rather than left implicit. + Authentication was the previous default; FN-8130 requires Settings to open on Appearance, the global Preferences section, when no explicit initialSection is supplied. + + FNXC:SettingsNavigation 2026-07-16-13:40: + Appearance is not behind the Advanced switch, so the landing section can never be one the operator has hidden; that is asserted here rather than left implicit. */ - it("defaults to the Authentication section when no initialSection is provided", async () => { + it("defaults to the Appearance section when no initialSection is provided", async () => { render( { ); await waitForSettingsModalReady(); - expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Appearance" })).toBeInTheDocument(); /* FNXC:SettingsNavigation 2026-07-16-01:30: - Asserts the landing section is REACHABLE, not that it is first in the nav: Authentication leads the AI & Models group (it belongs with the model settings it gates), so nav position is a layout choice that may move again. - What must hold is that the section Settings opens on is never one the Advanced switch hides — otherwise a default-configured operator lands on a section their own nav does not list. + The test asserts a reachable concrete landing surface rather than nav position. Appearance is in the Preferences group, and its placement may change, but the section Settings opens on must never be one the Advanced switch hides. */ expect(screen.getByRole("checkbox", { name: "Advanced settings" })).not.toBeChecked(); - expect(screen.getByRole("button", { name: /^Authentication$/ })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /^Appearance$/ })).toBeInTheDocument(); }); /* diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx index 7cdabdf970..ca2856d0d8 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.remote-notifications.test.tsx @@ -1543,8 +1543,8 @@ describe("SettingsModal", () => { it("falls back to first visible section when initial section is unavailable", async () => { renderModal({ initialSection: "unknown-section" as any }); await waitForSettingsModalReady(); - // FNXC:SettingsNavigation 2026-07-16-01:10: an unknown section still falls back to the first visible one; that is Authentication now, which is also where Settings opens. - expect(await screen.findByRole("heading", { name: "Authentication" })).toBeInTheDocument(); + // FNXC:SettingsNavigation 2026-07-16-13:40: FN-8130 keeps the unknown-section fallback on the always-visible Appearance default. + expect(await screen.findByRole("heading", { name: "Appearance" })).toBeInTheDocument(); }); }); diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx index cc62e6e3b1..8eaf3498e5 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.scheduling-merge.test.tsx @@ -1675,9 +1675,12 @@ describe("SettingsModal", () => { expect(screen.queryByRole("button", { name: /Research · Global/i })).not.toBeInTheDocument(); /* FNXC:SettingsNavigation 2026-07-16-01:10: - The fallback lands on Authentication, which is the first selectable section and where Settings opens. This asserted "General" back when that was the default landing section; the requirement is unchanged — an unreachable section falls back to the first selectable one — only which section that is has moved. + Authentication was the previous default landing section. The fallback requirement is unchanged: an unreachable section falls back to the configured default when it is selectable. + + FNXC:SettingsNavigation 2026-07-16-13:40: + FN-8130 makes Appearance the configured default and the first selectable Preferences section, so feature-gated research settings fall back there. */ - expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: "Appearance" })).toBeInTheDocument(); }); it("hides scheduled evals nav item when experimentalFeatures.evalsView is disabled", async () => { @@ -1714,8 +1717,8 @@ describe("SettingsModal", () => { await waitForSettingsModalReady(); expect(screen.queryByRole("button", { name: /Scheduled Evals/i })).not.toBeInTheDocument(); - // FNXC:SettingsNavigation 2026-07-16-01:10: falls back to Authentication — now the first selectable section and where Settings opens. Same requirement, different landing section. - expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument(); + // FNXC:SettingsNavigation 2026-07-16-13:40: FN-8130 falls back to Appearance, the configured default and first selectable Preferences section. + expect(screen.getByRole("heading", { name: "Appearance" })).toBeInTheDocument(); }); }); diff --git a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx index c289964c8e..9f908ef5ee 100644 --- a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx @@ -402,6 +402,19 @@ describe("SettingsModal mobile adaptations", () => { expect(queryByText("Settings Section", { selector: "label" })).toBeNull(); }); + /* + FNXC:SettingsNavigation 2026-07-16-13:40: + FN-8130 requires the mobile section picker and rendered content to start on Appearance when no explicit initialSection is supplied. Appearance is global and not Advanced-gated, so it remains selectable in the default visibility state. + */ + it("defaults the mobile section picker to Appearance", async () => { + mockSettingsViewport(true); + const { getByLabelText, getByRole } = render(); + await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); + + expect(getByLabelText("Settings Section")).toHaveValue("appearance"); + expect(getByRole("heading", { name: "Appearance" })).toBeInTheDocument(); + }); + it("keeps CLI Binary reachable from the Basic-mode mobile picker", async () => { localStorage.removeItem("fusion:settings:show-advanced"); mockSettingsViewport(true); @@ -590,10 +603,10 @@ describe("SettingsModal mobile adaptations", () => { /* FNXC:SettingsNavigation 2026-07-16-01:10: - Settings opens on Authentication, which renders provider cards rather than form - controls, so this navigates to a section that has some. The nav label is - "General · Project" now that the Global/Project pair is disambiguated — plain - "General" no longer matches any element. + Authentication was the previous default and renders provider cards rather than form controls. FN-8130 defaults Settings to Appearance, but this test deliberately navigates to a section with generic form controls. + + FNXC:SettingsNavigation 2026-07-16-13:40: + The nav label is "General · Project" now that the Global/Project pair is disambiguated — plain "General" no longer matches any element. */ const generalTabs = await findAllByText("General · Project"); await user.click(generalTabs[0]); @@ -609,12 +622,12 @@ describe("SettingsModal mobile adaptations", () => { */ it("shows scope on nav items and per-row badges rather than a section banner", async () => { const user = userEvent.setup(); - const { container, getByText } = render(); + const { container, getByRole } = render(); await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); expect(container.querySelectorAll(".settings-scope-icon").length).toBeGreaterThan(0); - await user.click(getByText("Appearance")); + await user.click(getByRole("button", { name: /Appearance$/ })); // The banner is gone for good — it asserted a single scope for a section // that genuinely mixes them.