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:
@@ -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).
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user