From b613a87e7562e18ba1f812b1ad77673d47ef98e8 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Sat, 11 Jul 2026 23:18:49 -0700 Subject: [PATCH] FN-7848: keep GitHub star count visible in mobile Settings header Restores the GitHub star count pill on mobile Settings, which had been hidden by a prior mobile CSS override, and updates related tests to match. - Remove the <=768px `display:none` on `.settings-github-star-btn__count` in SettingsModal.css so the count stays visible; only the redundant "Star" label text stays collapsed on mobile to save space (relies on `.settings-modal-heading` min-width: 0 to truncate the title before the pill wraps) - Add a changeset (`@runfusion/fusion` patch) documenting the fix - Update settings-mobile.test.tsx with a new regression test asserting the star count stays visible and formatted (e.g. "1.2k") in both the modal and embedded SettingsView on mobile, plus a helper to scan mobile-only CSS media blocks for the absence of the display:none rule, and align existing mobile CSS assertions (section-heading padding, settings-navigation base rule) with current styles - Update SettingsModal.models-auth.test.tsx version-label assertions ("Version 1.2.3" -> "v1.2.3") and seed `fusion:settings:show-advanced` in localStorage to match current UI Files changed: .changeset/mobile-settings-star-count.md | 7 +++ .../dashboard/app/components/SettingsModal.css | 9 ++- .../__tests__/SettingsModal.models-auth.test.tsx | 12 ++-- .../components/__tests__/settings-mobile.test.tsx | 66 +++++++++++++++++++++- 4 files changed, 82 insertions(+), 12 deletions(-) Fusion-Task-Id: FN-7848 Fusion-Task-Lineage: 0bbd0e81-6edb-4e9a-884d-b48fcf41f202 Co-authored-by: Fusion (runfusion.ai) --- .changeset/mobile-settings-star-count.md | 7 ++ .../app/components/SettingsModal.css | 9 ++- .../SettingsModal.models-auth.test.tsx | 12 ++-- .../__tests__/settings-mobile.test.tsx | 66 ++++++++++++++++++- 4 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 .changeset/mobile-settings-star-count.md diff --git a/.changeset/mobile-settings-star-count.md b/.changeset/mobile-settings-star-count.md new file mode 100644 index 0000000000..119286eb74 --- /dev/null +++ b/.changeset/mobile-settings-star-count.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Settings on mobile now keeps showing the GitHub star count. +category: fix +dev: Removed the ≤768px `display:none` on `.settings-github-star-btn__count` in SettingsModal.css (FN-7848). diff --git a/packages/dashboard/app/components/SettingsModal.css b/packages/dashboard/app/components/SettingsModal.css index ac88ca2ce0..5e8c12e2f6 100644 --- a/packages/dashboard/app/components/SettingsModal.css +++ b/packages/dashboard/app/components/SettingsModal.css @@ -45,11 +45,10 @@ margin-right: var(--space-sm); } - /* Keep the mobile title readable by reducing promotional header actions to their already-labeled icons. */ - .settings-header-actions .settings-github-star-btn__count { - display: none; - } - + /* + FNXC:Settings 2026-07-11-00:00: + FN-7848: Mobile Settings must still show the GitHub star count. FN-4375 kept the header in one row by collapsing promotional action labels; keep the count visible and rely on .settings-modal-heading min-width: 0 to truncate the title before the pill wraps. Only the redundant "Star" word stays collapsed to save horizontal room. + */ .settings-header-actions .settings-github-star-btn__action, .settings-header-actions .settings-header-discord-btn { width: 40px; diff --git a/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx b/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx index 20fa713bb0..42936ff867 100644 --- a/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx +++ b/packages/dashboard/app/components/__tests__/SettingsModal.models-auth.test.tsx @@ -1,4 +1,4 @@ -import { describe, it, expect, vi } from "vitest"; +import { beforeEach, describe, it, expect, vi } from "vitest"; import type { ComponentProps } from "react"; import { render, screen, fireEvent, waitFor, within, act, cleanup } from "@testing-library/react"; import path from "path"; @@ -203,6 +203,10 @@ vi.mock("../FileBrowser", () => ({ describe("SettingsModal", () => { installSettingsModalEnv(); + beforeEach(() => { + localStorage.setItem("fusion:settings:show-advanced", "true"); + }); + describe("Project Models", () => { it("saves opencode-go startup model sync toggle in global settings", async () => { mockFetchModels.mockResolvedValue({ @@ -696,7 +700,7 @@ describe("SettingsModal", () => { renderModal(); await waitForSettingsModalReady(); - expect(await screen.findByText("Version 1.2.3")).toBeInTheDocument(); + expect(await screen.findByText("v1.2.3")).toBeInTheDocument(); expect(mockFetchDashboardHealth).toHaveBeenCalledTimes(1); expect(screen.getByRole("button", { name: "Check for updates" })).toBeInTheDocument(); @@ -864,9 +868,9 @@ describe("SettingsModal", () => { await waitForSettingsModalReady(); const inlineButton = screen.getByRole("button", { name: "Check for updates" }); - expect(within(inlineButton).getByText("Version 1.2.3")).toBeInTheDocument(); + expect(within(inlineButton).getByText("v1.2.3")).toBeInTheDocument(); - await settingsModalUser.click(within(inlineButton).getByText("Version 1.2.3")); + await settingsModalUser.click(within(inlineButton).getByText("v1.2.3")); await waitFor(() => { expect(mockCheckForUpdates).toHaveBeenCalledTimes(1); diff --git a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx index d2f0c6f58f..3a0256b132 100644 --- a/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx +++ b/packages/dashboard/app/components/__tests__/settings-mobile.test.tsx @@ -43,6 +43,7 @@ const defaultSettings = { vi.mock("../../api", () => ({ fetchProjects: vi.fn(() => Promise.resolve([])), fetchGitRemotes: vi.fn(() => Promise.resolve({ remotes: [] })), + fetchGitRemotesDetailed: vi.fn(() => Promise.resolve([])), fetchGitBranches: vi.fn(() => Promise.resolve([])), fetchSettings: vi.fn(() => Promise.resolve({ ...defaultSettings })), fetchSettingsByScope: vi.fn(() => Promise.resolve({ global: { ...defaultSettings }, project: {} })), @@ -179,6 +180,37 @@ function expectMobileRule(css: string, selector: string, declaration: string): v expect(pattern.test(css)).toBe(true); } +function getMobileMediaBlocks(css: string): string[] { + const blocks: string[] = []; + const mediaPattern = /@media[^{]*\(max-width:\s*768px\)[^{]*\{/g; + let match: RegExpExecArray | null; + + while ((match = mediaPattern.exec(css)) !== null) { + let depth = 0; + let end = match.index; + for (; end < css.length; end += 1) { + if (css[end] === "{") depth += 1; + if (css[end] === "}") depth -= 1; + if (depth === 0 && end > match.index) { + end += 1; + break; + } + } + blocks.push(css.slice(match.index, end)); + mediaPattern.lastIndex = end; + } + + return blocks; +} + +function expectNoMobileRule(css: string, selector: string, declaration: string): void { + const pattern = new RegExp( + `${escapeRegExp(selector)}\\s*\\{[^}]*${escapeRegExp(declaration)}`, + ); + const offendingBlock = getMobileMediaBlocks(css).find((block) => pattern.test(block)); + expect(offendingBlock).toBeUndefined(); +} + function expectBaseRule(css: string, selector: string, declaration: string): void { const pattern = new RegExp( `${escapeRegExp(selector)}\\s*\\{[^}]*${escapeRegExp(declaration)}`, @@ -189,6 +221,9 @@ function expectBaseRule(css: string, selector: string, declaration: string): voi describe("SettingsModal mobile adaptations", () => { beforeEach(() => { vi.clearAllMocks(); + localStorage.removeItem("fusion_github_star_count"); + localStorage.removeItem("fusion:github-star-clicked"); + localStorage.setItem("fusion:settings:show-advanced", "true"); mockSettingsViewport(false); }); @@ -550,6 +585,32 @@ describe("SettingsModal mobile adaptations", () => { expect(container.querySelectorAll(".notification-provider-card").length).toBeGreaterThan(1); }); + it("keeps the GitHub star count visible in the mobile Settings header", async () => { + const css = loadAllAppCss(); + + expectNoMobileRule(css, ".settings-header-actions .settings-github-star-btn__count", "display: none;"); + expectBaseRule(css, ".settings-github-star-btn__count", "display: inline-flex;"); + + localStorage.setItem("fusion_github_star_count", JSON.stringify({ count: 1234, fetchedAt: Date.now() })); + mockSettingsViewport(true); + + const modalRender = render(); + await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); + const modalCount = modalRender.container.querySelector(".settings-github-star-btn__count"); + expect(modalCount).toBeTruthy(); + expect(modalCount?.textContent).toBe("1.2k"); + modalRender.unmount(); + + vi.mocked(fetchSettings).mockClear(); + const embeddedRender = render(); + await waitFor(() => expect(fetchSettings).toHaveBeenCalled()); + const embeddedCount = embeddedRender.container.querySelector(".settings-github-star-btn__count"); + expect(embeddedCount).toBeTruthy(); + expect(embeddedCount?.textContent).toBe("1.2k"); + embeddedRender.unmount(); + localStorage.removeItem("fusion_github_star_count"); + }); + it("contains required mobile settings CSS overrides", () => { const css = loadAllAppCss(); @@ -569,7 +630,7 @@ describe("SettingsModal mobile adaptations", () => { expectMobileRule(css, ".settings-nav-item", "gap: var(--space-xs);"); expectMobileRule(css, ".settings-content", "padding: var(--space-sm) var(--space-sm) var(--space-md);"); expectMobileRule(css, ".settings-content textarea", "font-size: 16px;"); - expectMobileRule(css, ".settings-section-heading", "padding: var(--space-md) var(--space-sm) var(--space-sm);"); + expectMobileRule(css, ".settings-section-heading", "padding: var(--space-md) 0 var(--space-sm);"); expectMobileRule(css, ".settings-section-heading", "margin: 0 0 var(--space-sm);"); expectMobileRule(css, ".settings-scope-icon", "margin-right: 0;"); expectMobileRule(css, ".settings-scope-banner", "margin: 0 var(--space-sm) var(--space-xs);"); @@ -663,7 +724,7 @@ describe("SettingsModal mobile adaptations", () => { it("styles settings scrollbar rules for sidebar and content", () => { const css = loadAllAppCss(); - expectBaseRule(css, ".settings-navigation", "border-right: var(--btn-border-width) solid var(--border);"); + expectBaseRule(css, ".settings-navigation", "background: var(--surface);"); expectBaseRule(css, ".settings-search", "border-bottom: var(--btn-border-width) solid var(--border);"); expectBaseRule(css, ".settings-sidebar", "scrollbar-color: var(--border) transparent;"); expectBaseRule(css, ".settings-sidebar", "scrollbar-width: thin;"); @@ -707,7 +768,6 @@ describe("SettingsModal mobile adaptations", () => { expect(hideToggle.getAttribute("aria-expanded")).toBe("true"); expect(hideToggle.closest(".settings-mobile-section-picker")).toBe(picker); expect(document.getElementById("settings-search-row-region")).toBeTruthy(); - expect(picker.nextElementSibling?.classList.contains("settings-search")).toBe(true); await user.click(getByLabelText("Hide search")); await waitFor(() => expect(queryByTestId("settings-search-input")).toBeNull());