FN-7552: prefix Authentication with Global in mobile settings picker

Fixes the mobile Settings section picker showing bare "Authentication" instead of "Global — Authentication" for the storage-less (scope: undefined) Authentication section.

- Add buildSettingsSectionGroupLabelMap to derive each section's owning group label (Global/Runtimes/Project) from SETTINGS_SECTIONS order
- Extend resolveSettingsSectionOptionLabel to fall back to a Global-derived prefix for storage-less sections belonging to the Global group, without affecting sections that already declare a scope
- Add regression test asserting the Authentication option renders "Global — Authentication" alongside existing scoped siblings
- Add changeset (patch) documenting the fix

Files changed:
 .changeset/fn-7552-mobile-authentication-global-prefix.md         |  7 +++
 packages/dashboard/app/components/SettingsModal.tsx               | 40 +++++++++++++
 packages/dashboard/app/components/__tests__/settings-mobile.test.tsx | 16 +++++
 3 files changed, 63 insertions(+)

Fusion-Task-Id: FN-7552

Fusion-Task-Lineage: c389f556-c7d3-48a2-a51d-926a70bdaa0e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-04 19:40:25 -07:00
parent d09b57fc66
commit 052a277725
3 changed files with 63 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Show the "Global" prefix on the Authentication entry in the mobile Settings picker.
category: fix
dev: resolveSettingsSectionOptionLabel now derives the Global-group prefix for storage-less (scope: undefined) sections in SettingsModal.tsx (FN-7552).

View File

@@ -292,6 +292,38 @@ function resolveFirstSelectableSettingsSection(sections: SettingsSection[], fall
return sections.find((section) => !section.isGroupHeader)?.id ?? fallback;
}
/*
FNXC:SettingsNavigation 2026-07-04-00:00:
The mobile Settings section picker (`<select>` on narrow viewports) prefixes every
section option with its owning group (`Global — `/`Project — `) so entries are
unambiguous when labels collide across scopes (e.g. "MCP Servers" exists in both
Global and Project). The Authentication section is intentionally `scope: undefined`
(it is not backed by settings storage — see SETTINGS_SECTIONS), but it still lives
under the Global group header in SETTINGS_SECTIONS, so its mobile option rendered as
bare "Authentication" instead of "Global — Authentication", inconsistent with its
Global-group siblings (FN-7552). SETTINGS_SECTION_GROUP_LABEL_BY_ID maps every
non-header section id to the label of the most recent group-header row preceding it
in SETTINGS_SECTIONS, so resolveSettingsSectionOptionLabel can fall back to a
group-derived "Global — " prefix for storage-less sections that belong to the Global
group — without changing behavior for any section that already declares a scope
(Runtimes entries keep their existing scope:"global" path) or for undefined-scope
group-header rows themselves (which are never rendered as selectable options).
*/
function buildSettingsSectionGroupLabelMap(sections: SettingsSection[]): Map<string, string> {
const map = new Map<string, string>();
let currentGroupLabel: string | undefined;
for (const section of sections) {
if (section.isGroupHeader) {
currentGroupLabel = section.label;
continue;
}
if (currentGroupLabel !== undefined) {
map.set(section.id, currentGroupLabel);
}
}
return map;
}
function resolveSettingsSectionOptionLabel(section: SettingsSection, label: string): string {
if (section.scope === "global") {
return `Global — ${label}`;
@@ -299,6 +331,9 @@ function resolveSettingsSectionOptionLabel(section: SettingsSection, label: stri
if (section.scope === "project") {
return `Project — ${label}`;
}
if (SETTINGS_SECTION_GROUP_LABEL_BY_ID.get(section.id) === "Global") {
return `Global — ${label}`;
}
return label;
}
@@ -384,6 +419,11 @@ const SETTINGS_SECTIONS: SettingsSection[] = [
{ id: "plugins", label: "Plugins", labelKey: "settings.nav.plugins", scope: "project", searchableText: ["Fusion plugins", "Pi extensions", "plugin manager", "extension marketplace"] },
];
// FNXC:SettingsNavigation 2026-07-04-00:00: sectionId -> owning group label ("Global"/"Runtimes"/"Project"),
// derived once from SETTINGS_SECTIONS order. Used by resolveSettingsSectionOptionLabel to prefix
// storage-less (scope: undefined) sections like "authentication" that belong to the Global group (FN-7552).
const SETTINGS_SECTION_GROUP_LABEL_BY_ID = buildSettingsSectionGroupLabelMap(SETTINGS_SECTIONS);
/** Well-known experimental feature flags with display labels.
* These always appear in the Experimental Features settings tab,
* regardless of whether they exist in the project's settings blob.

View File

@@ -323,6 +323,22 @@ describe("SettingsModal mobile adaptations", () => {
expect(getByText("No sections match this search.")).toBeTruthy();
});
// FN-7552: the Authentication section is storage-less (scope: undefined) but belongs to the
// Global group in SETTINGS_SECTIONS, so its mobile picker option must still carry the
// "Global — " prefix like its Global-group siblings, without changing scoped sibling labels.
it("prefixes the storage-less Authentication section with 'Global —' in the mobile picker", async () => {
mockSettingsViewport(true);
const { getByLabelText } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
const picker = getByLabelText("Settings Section") as HTMLSelectElement;
const optionByValue = (value: string) => Array.from(picker.options).find((opt) => opt.value === value);
expect(optionByValue("authentication")?.textContent).toBe("Global — Authentication");
expect(optionByValue("global-mcp")?.textContent).toBe("Global — MCP Servers");
expect(optionByValue("mcp")?.textContent).toBe("Project — MCP Servers");
});
it("can open memory settings from the mobile section picker", async () => {
mockSettingsViewport(true);
const user = userEvent.setup();