FN-8128: restore CLI Binary to basic Settings
Restore the CLI Binary settings panel to the default-visible Settings navigation. - Remove CLI Binary from Advanced-only sections and place it in the Global group. - Cover desktop navigation, search, Advanced mode, and mobile picker visibility. - Add a patch changeset for the restored default access. Files changed: .changeset/fn-8128-cli-binary-out-of-advanced.md | 7 ++ .../dashboard/app/components/SettingsModal.tsx | 14 ++- .../SettingsModal.cliBinaryVisibility.test.tsx | 124 +++++++++++++++++++++ .../components/__tests__/settings-mobile.test.tsx | 15 +++ .../settings/sections/CliBinarySection.tsx | 6 +- 5 files changed, 156 insertions(+), 10 deletions(-) Fusion-Task-Id: FN-8128 Fusion-Task-Lineage: aa6e5c11-1186-4213-9b1a-2582e892d363 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8128-cli-binary-out-of-advanced.md
Normal file
7
.changeset/fn-8128-cli-binary-out-of-advanced.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Show the CLI Binary panel in default Settings instead of behind the Advanced switch.
|
||||
category: fix
|
||||
dev: Removed `cli-binary` from `ADVANCED_SETTINGS_SECTION_IDS` and relocated its navigation entry.
|
||||
@@ -297,6 +297,9 @@ const SETTINGS_NAV_MAX_WIDTH = 420;
|
||||
/*
|
||||
FNXC:SettingsSimplification 2026-07-10-23:24:
|
||||
Settings opens in a focused mode that omits specialist integration, runtime, diagnostics, and infrastructure sections. The Advanced settings switch restores every section, applies consistently to desktop navigation, mobile navigation, and search, and persists only as a browser-local display preference so it never changes or exports project settings.
|
||||
|
||||
FNXC:SettingsNavigation 2026-07-16-12:00:
|
||||
FN-8128 returns CLI Binary to the default Settings view. It is deliberately absent from this Advanced-only set so desktop navigation, the mobile picker, and search expose binary install and diagnostic controls without requiring the browser-local Advanced preference.
|
||||
*/
|
||||
const ADVANCED_SETTINGS_SECTION_IDS = new Set([
|
||||
"node-sync",
|
||||
@@ -318,7 +321,6 @@ const ADVANCED_SETTINGS_SECTION_IDS = new Set([
|
||||
"mcp",
|
||||
"prompts",
|
||||
"plugins",
|
||||
"cli-binary",
|
||||
]);
|
||||
|
||||
function readAdvancedSettingsPreference(): boolean {
|
||||
@@ -468,6 +470,11 @@ export const SETTINGS_SECTIONS: SettingsSection[] = [
|
||||
{ id: "keyboard-shortcuts", label: "Keyboard Shortcuts", labelKey: "settings.nav.keyboardShortcuts", scope: "global", searchableText: ["keyboard shortcuts", "hotkeys", "quick chat shortcut", "terminal shortcut", "open files", "open settings", "command center", "new task shortcut", "record shortcut"] },
|
||||
{ id: "notifications", label: "Notifications", labelKey: "settings.nav.notifications", scope: "global", searchableText: ["ntfy", "webhook", "events", "failure notifications", "sticky", "toast"] },
|
||||
{ id: "global-general", label: "General · Global", labelKey: "settings.nav.globalGeneral", scope: "global", searchableText: ["global defaults", "modal outside dismiss", "agent logs", "persist tool output", "thinking logs"] },
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-12:00:
|
||||
FN-8128 keeps the `fn` binary panel as a dedicated section rather than re-inlining machine plumbing at the top of General · Global, while restoring it to the default-visible Global group. Operators need installation, version, path, and diagnostic controls in Basic mode when setup or repair is needed.
|
||||
*/
|
||||
{ id: "cli-binary", label: "CLI Binary", labelKey: "settings.nav.cliBinary", scope: "global", searchableText: ["fn binary", "cli", "install", "version", "path", "upgrade", "homebrew", "binary check"] },
|
||||
|
||||
{ id: "__project_header", label: "Project", labelKey: "settings.nav.projectHeader", scope: undefined, isGroupHeader: true },
|
||||
/*
|
||||
@@ -636,11 +643,6 @@ export const SETTINGS_SECTIONS: SettingsSection[] = [
|
||||
|
||||
{ id: "__advanced_header", label: "Advanced", labelKey: "settings.nav.advancedHeader", scope: undefined, isGroupHeader: true },
|
||||
{ id: "experimental", label: "Experimental Features", labelKey: "settings.nav.experimental", scope: "global", searchableText: ["feature flags", "experiments", "research view", "evals view", "sandbox", "subtask breakdown"] },
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:00:
|
||||
Last entry in the nav, and advanced-only. The `fn` binary panel used to render at the TOP of "General · Global", so machine plumbing was the first thing an operator saw on opening Settings. It is install/version/path maintenance touched once or when something breaks — the definition of what the Advanced switch hides.
|
||||
*/
|
||||
{ id: "cli-binary", label: "CLI Binary", labelKey: "settings.nav.cliBinary", scope: "global", searchableText: ["fn binary", "cli", "install", "version", "path", "upgrade", "homebrew", "binary check"] },
|
||||
];
|
||||
|
||||
// FNXC:SettingsNavigation 2026-07-04-00:00: sectionId -> owning group label ("Global"/"Runtimes"/"Project"),
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { SettingsModal } from "../SettingsModal";
|
||||
|
||||
const mockFetchSettings = vi.fn();
|
||||
const mockFetchSettingsByScope = vi.fn();
|
||||
|
||||
vi.mock("../../api", async (importOriginal) => {
|
||||
const { createDashboardApiMock } = await import("../../test/mockApi");
|
||||
return createDashboardApiMock(() => importOriginal<typeof import("../../api")>(), {
|
||||
fetchSettings: (...args: unknown[]) => mockFetchSettings(...args),
|
||||
fetchSettingsByScope: (...args: unknown[]) => mockFetchSettingsByScope(...args),
|
||||
});
|
||||
});
|
||||
|
||||
vi.mock("../../api/legacy", () => ({
|
||||
fetchFnBinaryStatus: vi.fn(() => Promise.resolve({
|
||||
binary: { binary: "fn", installed: false, path: null, version: null },
|
||||
expectedVersion: "1.2.3",
|
||||
state: "missing",
|
||||
install: { command: "npm install -g runfusion.ai" },
|
||||
})),
|
||||
installFnBinary: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../hooks/useMemoryBackendStatus", () => ({
|
||||
useMemoryBackendStatus: () => ({ status: null, capabilities: null, loading: false, error: null, refresh: vi.fn() }),
|
||||
}));
|
||||
vi.mock("../../hooks/useViewportMode", () => ({
|
||||
MOBILE_MEDIA_QUERY: "(max-width: 768px), (max-height: 480px)",
|
||||
useViewportMode: () => "desktop",
|
||||
getViewportMode: () => "desktop",
|
||||
isMobileViewport: () => false,
|
||||
}));
|
||||
vi.mock("../../hooks/useMobileKeyboard", () => ({
|
||||
useMobileKeyboard: () => ({ keyboardOverlap: 0, viewportHeight: null, viewportOffsetTop: 0, keyboardOpen: false }),
|
||||
}));
|
||||
vi.mock("../../hooks/useMobileScrollLock", () => ({
|
||||
useMobileScrollLock: vi.fn(),
|
||||
useMobileKeyboardViewportLock: vi.fn(),
|
||||
useMobileViewportRestoreReset: vi.fn(),
|
||||
}));
|
||||
vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: vi.fn() }) }));
|
||||
vi.mock("../../hooks/useWorkspaceFileBrowser", () => ({
|
||||
useWorkspaceFileBrowser: () => ({ entries: [], currentPath: ".", setPath: vi.fn(), loading: false, error: null, refresh: vi.fn() }),
|
||||
}));
|
||||
vi.mock("../../hooks/useWorktrunkInstallStatus", () => ({
|
||||
useWorktrunkInstallStatus: () => ({ status: "idle", requestInstall: vi.fn() }),
|
||||
}));
|
||||
|
||||
function buildSettings() {
|
||||
return {
|
||||
autoMerge: true,
|
||||
maxConcurrent: 2,
|
||||
maxTriageConcurrent: 2,
|
||||
maxWorktrees: 4,
|
||||
pollIntervalMs: 15000,
|
||||
heartbeatMultiplier: 1,
|
||||
groupOverlappingFiles: true,
|
||||
overlapIgnorePaths: [],
|
||||
mergeStrategy: "direct",
|
||||
mergeIntegrationWorktree: "reuse-task-worktree",
|
||||
recycleWorktrees: false,
|
||||
executorAllowSiblingBranchRename: false,
|
||||
worktreeNaming: "random",
|
||||
worktreesDir: "",
|
||||
worktrunk: { enabled: false, binaryPath: "", onFailure: "fail" },
|
||||
includeTaskIdInCommit: true,
|
||||
ntfyEnabled: false,
|
||||
failureNotificationMode: "sticky-only",
|
||||
failureNotificationDelayMs: 30000,
|
||||
webhookEnabled: false,
|
||||
experimentalFeatures: {},
|
||||
};
|
||||
}
|
||||
|
||||
async function renderBasicSettings() {
|
||||
const view = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
|
||||
await waitFor(() => expect(mockFetchSettings).toHaveBeenCalled());
|
||||
return view;
|
||||
}
|
||||
|
||||
describe("SettingsModal CLI Binary visibility", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.removeItem("fusion:settings:show-advanced");
|
||||
mockFetchSettings.mockResolvedValue(buildSettings());
|
||||
mockFetchSettingsByScope.mockResolvedValue({ global: {}, project: {} });
|
||||
});
|
||||
|
||||
it("keeps CLI Binary reachable from the Basic-mode desktop nav", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = await renderBasicSettings();
|
||||
|
||||
expect(screen.getByRole("checkbox", { name: "Advanced settings" })).not.toBeChecked();
|
||||
expect(container.querySelector(".settings-mobile-section-picker")).toBeNull();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /CLI Binary$/ }));
|
||||
expect(await screen.findByText(/Installing the global CLI lets you run fn and fusion/)).toBeInTheDocument();
|
||||
expect(container.querySelector(".cli-binary-panel")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("returns CLI Binary for a Basic-mode search and lets operators open it", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { container } = await renderBasicSettings();
|
||||
|
||||
expect(container.querySelector(".settings-mobile-section-picker")).toBeNull();
|
||||
await user.type(screen.getByTestId("settings-search-input"), "binary check");
|
||||
|
||||
const cliBinaryNav = await screen.findByRole("button", { name: /CLI Binary$/ });
|
||||
await user.click(cliBinaryNav);
|
||||
expect(await screen.findByText(/Installing the global CLI lets you run fn and fusion/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps CLI Binary available after Advanced settings is enabled", async () => {
|
||||
const user = userEvent.setup();
|
||||
await renderBasicSettings();
|
||||
|
||||
await user.click(screen.getByRole("checkbox", { name: "Advanced settings" }));
|
||||
expect(screen.getByRole("checkbox", { name: "Advanced settings" })).toBeChecked();
|
||||
expect(screen.getByRole("button", { name: /CLI Binary$/ })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -402,6 +402,21 @@ describe("SettingsModal mobile adaptations", () => {
|
||||
expect(queryByText("Settings Section", { selector: "label" })).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps CLI Binary reachable from the Basic-mode mobile picker", async () => {
|
||||
localStorage.removeItem("fusion:settings:show-advanced");
|
||||
mockSettingsViewport(true);
|
||||
const user = userEvent.setup();
|
||||
const { container, getByLabelText, findByText } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
const picker = getByLabelText("Settings Section") as HTMLSelectElement;
|
||||
expect(Array.from(picker.options).map((option) => option.value)).toContain("cli-binary");
|
||||
|
||||
await user.selectOptions(picker, "cli-binary");
|
||||
expect(await findByText(/Installing the global CLI lets you run fn and fusion/)).toBeTruthy();
|
||||
expect(container.querySelector(".cli-binary-panel")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("excludes research sections from mobile picker when researchView is disabled", async () => {
|
||||
mockSettingsViewport(true);
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -2,10 +2,8 @@ import { useTranslation } from "react-i18next";
|
||||
import { CliBinaryPanel } from "../../CliBinaryPanel";
|
||||
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:00:
|
||||
The `fn` CLI binary panel gets its own advanced-only section at the bottom of the nav instead of sitting at the TOP of "General · Global".
|
||||
It was the first thing an operator saw when Settings opened — a binary install/version/path panel above the app preferences most people came for. It is machine-level plumbing an operator touches once (or when an install breaks), not a preference, so it belongs behind the Advanced switch with the other specialist surfaces rather than in the default-visible set.
|
||||
Placed last, in the Advanced group, because "least often needed" is exactly the ordering that group encodes.
|
||||
FNXC:SettingsNavigation 2026-07-16-12:00:
|
||||
FN-8128 keeps the `fn` CLI binary panel in its own dedicated, default-visible Settings section. It must not be re-inlined at the top of "General · Global", but operators need install, version, path, and diagnostic controls available in Basic mode when setup or repair is needed.
|
||||
*/
|
||||
export function CliBinarySection() {
|
||||
const { t } = useTranslation("app");
|
||||
|
||||
Reference in New Issue
Block a user