FN-8528: normalize Settings navigation icon sizing

Normalize Settings navigation icons into uniform fixed slots across desktop rows.

- Move scope-icon styling into the Settings modal component stylesheet.
- Reserve consistent non-shrinking icon dimensions for custom and fallback section icons.
- Add desktop and mobile regression coverage for icon-slot behavior.

Files changed:
 .../dashboard/app/components/SettingsModal.css     | 14 +++++++++----
 .../__tests__/SettingsModal.navResize.test.tsx     | 24 ++++++++++++++++++++++
 .../components/__tests__/settings-mobile.test.tsx  |  3 ++-
 packages/dashboard/app/styles.css                  | 12 -----------
 4 files changed, 36 insertions(+), 17 deletions(-)

Fusion-Task-Id: FN-8528

Fusion-Task-Lineage: fd78495d-a2d3-4786-bc1a-919d51049e66

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-22 23:10:18 -07:00
parent 64661c3ae1
commit 7a552832f4
4 changed files with 36 additions and 17 deletions

View File

@@ -1369,11 +1369,17 @@ Settings section headings should preserve hierarchy through spacing and type onl
padding-bottom: var(--space-md);
}
/* Scope indicators in sidebar nav items */
/*
FNXC:SettingsNavigation 2026-07-22-23:05:
Desktop Settings rows render custom section glyphs alongside global Globe and project Folder fallbacks.
Every rendered glyph must occupy the same non-shrinking Lucide-sized box so row labels share one
start edge; mobile hides the sidebar and keeps its icon-free picker without creating a blank slot.
*/
.settings-scope-icon {
margin-right: 6px;
display: inline-flex;
vertical-align: middle;
inline-size: var(--space-md);
block-size: var(--space-md);
flex: 0 0 var(--space-md);
margin-right: var(--space-xs);
color: var(--text-muted);
}

View File

@@ -258,6 +258,30 @@ describe("SettingsModal navigation rail resize", () => {
expect(getSettingsNavigation().style.getPropertyValue("--settings-nav-width")).toBe("");
});
it("uses one fixed non-shrinking icon slot for fallback and custom desktop rows", async () => {
const iconBlock = getCssBlock(settingsModalCss, ".settings-scope-icon");
expect(iconBlock).toContain("inline-size: var(--space-md);");
expect(iconBlock).toContain("block-size: var(--space-md);");
expect(iconBlock).toContain("flex: 0 0 var(--space-md);");
renderModal({ initialSection: "appearance" });
await waitForSettingsModalReady();
const fallbackGlobal = screen.getByRole("button", { name: /Appearance$/ });
const fallbackProject = screen.getByRole("button", { name: /General · Project$/ });
const customIcon = screen.getByRole("button", { name: /Source Control · Global$/ });
expect(fallbackGlobal?.querySelector("svg.settings-scope-icon")).toBeTruthy();
expect(fallbackProject?.querySelector("svg.settings-scope-icon")).toBeTruthy();
expect(customIcon?.querySelector("svg.settings-scope-icon")).toBeTruthy();
const preferencesHeader = screen.getByText("Preferences").closest(".settings-group-header");
expect(preferencesHeader?.querySelector(".settings-scope-icon")).toBeNull();
fireEvent.change(screen.getByTestId("settings-search-input"), { target: { value: "source control" } });
await waitFor(() => expect(screen.getByRole("button", { name: /Source Control · Global$/ }).querySelector("svg.settings-scope-icon")).toBeTruthy());
expect(screen.queryByText("Preferences")).not.toBeInTheDocument();
});
it("does not render the resize handle when the Settings media query matches mobile", async () => {
setMatchMediaMatches(true);

View File

@@ -118,6 +118,7 @@ vi.mock("../../api", () => ({
fetchSystemInfo: vi.fn(() => Promise.resolve({ supervised: true, restartSupported: true })),
requestSystemRestart: vi.fn(() => Promise.resolve({ scheduled: true })),
fetchGlobalSettings: vi.fn(() => Promise.resolve({ ...defaultSettings })),
listDiscussionCategories: vi.fn(() => Promise.resolve({ categories: [] })),
// SettingsModal renders ProjectDefaultWorkflowField → WorkflowSelector, which loads these on mount.
fetchWorkflows: vi.fn(() => Promise.resolve([])),
fetchProjectDefaultWorkflow: vi.fn(() => Promise.resolve({ workflowId: null })),
@@ -403,6 +404,7 @@ describe("SettingsModal mobile adaptations", () => {
expect(picker.getAttribute("aria-label")).toBe("Settings Section");
expect(container.querySelector('label[for="settings-mobile-section"]')).toBeNull();
expect(queryByText("Settings Section", { selector: "label" })).toBeNull();
expect(picker.closest(".settings-mobile-section-picker")?.querySelector(".settings-scope-icon")).toBeNull();
});
/*
@@ -540,7 +542,6 @@ describe("SettingsModal mobile adaptations", () => {
expect(queryByLabelText("Push Remote")).toBeNull();
expect(queryByText("Git remote to push to")).toBeNull();
await user.click(getByRole("button", { name: "Save" }));
await waitFor(() => expect(updateSettings).toHaveBeenCalled());
const payload = vi.mocked(updateSettings).mock.calls[0][0] as Record<string, unknown>;

View File

@@ -1839,14 +1839,6 @@ FN-7825 makes .settings-navigation the sole owner of Settings rail width and rem
}
/* Scope indicators in sidebar nav items */
.settings-scope-icon {
margin-right: 6px;
display: inline-flex;
vertical-align: middle;
color: var(--text-muted);
}
/* Scope banner above section content */
/* Helper note styling for Settings sections — aligns with form-group horizontal gutters */
@@ -3846,10 +3838,6 @@ Toast text must contrast its status background across every dashboard theme and
display: none;
}
.settings-scope-icon {
margin-right: 0;
}
.settings-content {
flex: 1;
min-height: 0;