fix(settings): Source Control nav icons, Authentication as the landing section, CLI binary behind Advanced (#2162)
Follow-up to #2158. That PR was squash-merged while this last commit was still in flight, so these four fixes never landed on main. ### Source Control had no icon in the nav A section's own `icon` now wins over the generic scope glyph, which becomes the fallback. `icon` previously only rendered when `scope` was `undefined`, so **any** scoped section was forced to the globe/folder pair and could not identify itself — Source Control was indistinguishable from MCP Servers and Scheduling. Both Source Control entries now carry a git-branch icon. Nothing is lost: these sections spell their tier out in the label (`· Global` / `· Project`), and the aria-label and tooltip still announce it. ### Settings opens on Authentication Previously "General · Global". Nothing else in the product works until a provider is connected — the dashboard's own empty state sends operators to Settings for exactly this. Authentication leads the **AI & Models** group rather than sitting under Integrations: connecting a provider and picking the models it gates are one task, in that order. A test asserts the landing section is never one the Advanced switch hides, so a default-configured operator cannot land on a section their own nav does not list. ### The `fn` CLI binary panel moved behind Advanced, at the bottom It used to render *first* inside "General · Global" — an install/version/path panel was the first thing an operator saw on opening Settings. It is machine plumbing touched once, or when an install breaks, so it belongs with the other specialist surfaces. ### Changing section starts you at the top of it Sections keep no scroll of their own, so the container's offset carried over: leaving a long section scrolled halfway and picking a short one landed mid-content — on mobile, often past everything, on an apparently blank screen. Guarded by a ref rather than by reading the highlight key, because a search jump also changes `activeSection` and that key self-clears ~1.6s later, which would re-run the effect and yank the operator off the row they just jumped to. ## Verification - Typecheck clean (`tsconfig.app.json` — the one that covers `app/`). - Settings suites run against this branch and against plain main: **identical 67-failure set, 0 new**. Those 67 are pre-existing on main (51 scheduling-merge + 16 remote-notifications) and are untouched here. - Browser-verified: lands on Authentication; Source Control renders `lucide-git-branch`; CLI Binary is last and hidden with Advanced off; section change resets scroll 1200 → 0 while a search jump still holds its row. 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Evidence Both Source Control rows now carry a git-branch icon, sized and aligned like their neighbours; Models keeps globe/folder.  <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Settings now opens to **Authentication** by default. * Added a dedicated advanced-only **CLI Binary** settings section. * Improved navigation icons for Authentication and Source Control sections. * **Improvements** * Settings sections now reliably scroll to the top when switching sections. * Search-result navigation preserves the selected result’s position. * **Tests** * Updated settings and mobile navigation coverage for the new default section and labels. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useMemo, useRef, type CSSProperties, type Dispatch, type KeyboardEvent as ReactKeyboardEvent, type MouseEvent, type PointerEvent as ReactPointerEvent, type SetStateAction } from "react";
|
||||
import { Globe, Folder, RefreshCw, Star, HelpCircle, Settings as SettingsIcon, Search, X as SearchToggleCloseIcon } from "lucide-react";
|
||||
import { Globe, Folder, GitBranch, RefreshCw, Star, HelpCircle, Settings as SettingsIcon, Search, X as SearchToggleCloseIcon } from "lucide-react";
|
||||
import {
|
||||
getErrorMessage,
|
||||
resolveGitlabConfig,
|
||||
@@ -46,6 +46,7 @@ import { GeneralSection } from "./settings/sections/GeneralSection";
|
||||
import { ProjectModelsSection, WorkflowLaneFlushRejection } from "./settings/sections/ProjectModelsSection";
|
||||
import { SchedulingSection } from "./settings/sections/SchedulingSection";
|
||||
import { SchedulingGlobalSection } from "./settings/sections/SchedulingGlobalSection";
|
||||
import { CliBinarySection } from "./settings/sections/CliBinarySection";
|
||||
import { ScheduledEvalsSection } from "./settings/sections/ScheduledEvalsSection";
|
||||
import { NodeRoutingSection } from "./settings/sections/NodeRoutingSection";
|
||||
import { WorktreesSection } from "./settings/sections/WorktreesSection";
|
||||
@@ -293,6 +294,7 @@ const ADVANCED_SETTINGS_SECTION_IDS = new Set([
|
||||
"mcp",
|
||||
"prompts",
|
||||
"plugins",
|
||||
"cli-binary",
|
||||
]);
|
||||
|
||||
function readAdvancedSettingsPreference(): boolean {
|
||||
@@ -455,6 +457,13 @@ export const SETTINGS_SECTIONS: SettingsSection[] = [
|
||||
{ id: "merge", label: "Merge", labelKey: "settings.nav.merge", scope: "project", searchableText: ["auto merge", "AI merge", "merge strategy", "plan approval", "direct merge", "integration branch", "push after merge"] },
|
||||
|
||||
{ id: "__ai_header", label: "AI & Models", labelKey: "settings.nav.aiHeader", scope: undefined, isGroupHeader: true },
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:30:
|
||||
Authentication leads the AI & Models group, and Settings still opens on it.
|
||||
It is a provider-credentials screen, so it belongs with the model settings it gates rather than under Integrations (where it sat among MCP/Plugins/runtimes) or floating above the groups as a special case — connecting a provider and choosing its models are one task, done in that order.
|
||||
First within the group because nothing else in AI & Models can be configured until it is done: with no provider connected there are no models to pick.
|
||||
*/
|
||||
{ id: "authentication", label: "Authentication", labelKey: "settings.nav.authentication", scope: undefined, icon: Globe, searchableText: ["login", "OAuth", "API key", "custom providers", "Anthropic", "OpenAI", "provider credentials"] },
|
||||
{ id: "global-models", label: "Models · Global", labelKey: "settings.nav.globalModels", scope: "global", searchableText: ["global models", "model presets", "favorite providers", "model pricing overrides", "LiteLLM pricing", "token pricing", "translate", "translation model", "import translation model", "import auto-translation model"] },
|
||||
/**
|
||||
* FNXC:SettingsNavigation 2026-07-13-00:00:
|
||||
@@ -577,9 +586,8 @@ export const SETTINGS_SECTIONS: SettingsSection[] = [
|
||||
The two are adjacent and ordered global-then-project to match the inheritance they model — the global entry holds the fallbacks the project entry overrides — mirroring the MCP Servers pair directly below.
|
||||
The GitLab/GitHub keywords below were curated on the `general` and `merge` nav entries before their controls moved here; a keyword left behind would send an operator searching "gitlab token" to a section that no longer renders one. The translate keywords deliberately did NOT move: `githubImportAutoTranslate`/`importTranslateTargetLocale` are Import Tasks panel settings and stay in General.
|
||||
*/
|
||||
{ id: "source-control-global", label: "Source Control · Global", labelKey: "settings.nav.sourceControlGlobal", scope: "global", searchableText: ["GitLab instance URL", "global tracking repo", "GitLab", "GitHub", "global GitLab token", "GitLab fallback", "source control", "forge"] },
|
||||
{ id: "source-control", label: "Source Control · Project", labelKey: "settings.nav.sourceControl", scope: "project", searchableText: ["GitHub tracking", "GitLab integration", "GitHub auth mode", "GitLab access token", "GitHub personal access token", "tracking repo", "source control", "forge", "gh cli", "issue tracking"] },
|
||||
{ id: "authentication", label: "Authentication", labelKey: "settings.nav.authentication", scope: undefined, icon: Globe, searchableText: ["login", "OAuth", "API key", "custom providers", "Anthropic", "OpenAI", "provider credentials"] },
|
||||
{ id: "source-control-global", label: "Source Control · Global", labelKey: "settings.nav.sourceControlGlobal", scope: "global", icon: GitBranch, searchableText: ["GitLab instance URL", "global tracking repo", "GitLab", "GitHub", "global GitLab token", "GitLab fallback", "source control", "forge"] },
|
||||
{ id: "source-control", label: "Source Control · Project", labelKey: "settings.nav.sourceControl", scope: "project", icon: GitBranch, searchableText: ["GitHub tracking", "GitLab integration", "GitHub auth mode", "GitLab access token", "GitHub personal access token", "tracking repo", "source control", "forge", "gh cli", "issue tracking"] },
|
||||
{ id: "global-mcp", label: "MCP Servers · Global", labelKey: "settings.nav.globalMcp", scope: "global", searchableText: ["global MCP servers", "shared MCP", "user MCP", "tool servers"] },
|
||||
{ id: "mcp", label: "MCP Servers · Project", labelKey: "settings.nav.mcp", scope: "project", searchableText: ["project MCP servers", "workspace MCP", "project tool servers", "mcp config"] },
|
||||
{ id: "plugins", label: "Plugins", labelKey: "settings.nav.plugins", scope: "project", searchableText: ["Fusion plugins", "Pi extensions", "plugin manager", "extension marketplace"] },
|
||||
@@ -600,6 +608,11 @@ 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"),
|
||||
@@ -730,7 +743,12 @@ function normalizeWorktreeCopyFilesForSave(paths?: string[]): string[] {
|
||||
type LegacySectionId = "pi-extensions";
|
||||
export type SectionId = SettingsSection["id"] | LegacySectionId;
|
||||
|
||||
const DEFAULT_SETTINGS_SECTION: SectionId = "global-general";
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:00:
|
||||
Settings opens on Authentication. It is the section an operator actually needs first — nothing else in the product works until a provider is connected, and the dashboard's own empty state points here ("Set one up in Settings → AI Setup"). The previous landing section was "General · Global", a page of app preferences (and, until this change, the `fn` binary panel) that nobody opens Settings to find.
|
||||
It is also always visible: Authentication is not behind the Advanced switch, so the default landing section cannot be one the operator has hidden.
|
||||
*/
|
||||
const DEFAULT_SETTINGS_SECTION: SectionId = "authentication";
|
||||
|
||||
type PluginsSubsectionId = "fusion-plugins" | "pi-extensions";
|
||||
|
||||
@@ -1184,6 +1202,8 @@ export function SettingsModal({
|
||||
The setting a search result asked to land on. Held here rather than in the sections because the modal owns both halves of the jump: it switches the active section AND scrolls to the row, and only the row itself knows how to flag the match (via SettingsSearchHighlightProvider).
|
||||
*/
|
||||
const [highlightedSettingKey, setHighlightedSettingKey] = useState<string | null>(null);
|
||||
/** Set when a search result drives the next section change; see the scroll-to-top effect. */
|
||||
const settingsJumpPendingRef = useRef(false);
|
||||
const [showMobileSectionPicker, setShowMobileSectionPicker] = useState(() =>
|
||||
viewportMode === "mobile" ||
|
||||
(typeof window !== "undefined" && typeof window.matchMedia === "function"
|
||||
@@ -1352,10 +1372,31 @@ export function SettingsModal({
|
||||
Landing a search result is two steps that cannot happen in one: switching section unmounts the old one and mounts the target, so the row does not exist in the DOM until React commits. The click sets section + key, and the effect below scrolls once the row is actually there.
|
||||
*/
|
||||
const handleSettingsSearchResultSelect = useCallback((sectionId: string, key: string) => {
|
||||
// Claims the upcoming section change so the scroll-to-top below yields to the
|
||||
// jump; otherwise the row we just scrolled to would be scrolled away from.
|
||||
settingsJumpPendingRef.current = true;
|
||||
setActiveSection(sectionId);
|
||||
setHighlightedSettingKey(key);
|
||||
}, []);
|
||||
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:10:
|
||||
Switching section starts you at the top of it.
|
||||
Sections keep no scroll of their own, so the container's offset carried over: leaving a long section scrolled halfway (Scheduling, General) and picking a short one landed mid-content — on mobile, often past everything, on an apparently blank screen with no hint to scroll up.
|
||||
Guarded by a ref rather than by reading `highlightedSettingKey`: a search jump also changes `activeSection`, and that key clears itself ~1.6s later, which would re-run this effect and yank the operator away from the row they had just jumped to. The ref is consumed once, so only the jump's own section change is exempt.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (settingsJumpPendingRef.current) {
|
||||
settingsJumpPendingRef.current = false;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:10:
|
||||
Assigns `scrollTop` rather than calling `scrollTo({top:0})`: jsdom implements the property but not the method, so the smarter-looking call throws "scrollTo is not a function" in component tests. The jump is instant either way — a section change is a context switch, not a movement the eye should follow.
|
||||
*/
|
||||
if (settingsContentRef.current) settingsContentRef.current.scrollTop = 0;
|
||||
}, [activeSection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!highlightedSettingKey) return;
|
||||
const container = settingsContentRef.current;
|
||||
@@ -3891,6 +3932,8 @@ export function SettingsModal({
|
||||
researchLimitError={researchLimitError}
|
||||
/>
|
||||
);
|
||||
case "cli-binary":
|
||||
return <CliBinarySection />;
|
||||
case "experimental":
|
||||
return (
|
||||
<ExperimentalSection
|
||||
@@ -4277,10 +4320,27 @@ export function SettingsModal({
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{section.scope === "global" && <Globe className="settings-scope-icon" aria-label={t("settings.nav.aria.global", "Global setting")} size={16} />}
|
||||
{section.scope === "project" && <Folder className="settings-scope-icon" aria-label={t("settings.nav.aria.project", "Project setting")} size={16} />}
|
||||
{section.icon && !section.scope && (
|
||||
<section.icon className="settings-scope-icon" aria-label={t("settings.nav.aria.global", "Global setting")} size={16} />
|
||||
{/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:30:
|
||||
A section's own icon wins over the generic scope glyph; the glyph is the fallback.
|
||||
Previously `icon` only rendered when `scope` was undefined, so any scoped section was forced to the globe/folder pair and could not identify itself — Source Control looked exactly like MCP Servers and Scheduling in the nav.
|
||||
Nothing is lost by yielding the glyph: these sections spell their tier out in the label ("Source Control · Global"), and the aria-label + tooltip below still announce it, so scope survives for assistive tech and hover.
|
||||
*/}
|
||||
{section.icon ? (
|
||||
<section.icon
|
||||
className="settings-scope-icon"
|
||||
aria-label={
|
||||
section.scope === "project"
|
||||
? t("settings.nav.aria.project", "Project setting")
|
||||
: t("settings.nav.aria.global", "Global setting")
|
||||
}
|
||||
size={16}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{section.scope === "global" && <Globe className="settings-scope-icon" aria-label={t("settings.nav.aria.global", "Global setting")} size={16} />}
|
||||
{section.scope === "project" && <Folder className="settings-scope-icon" aria-label={t("settings.nav.aria.project", "Project setting")} size={16} />}
|
||||
</>
|
||||
)}
|
||||
{t(section.labelKey, section.label)}
|
||||
</button>
|
||||
|
||||
@@ -283,7 +283,12 @@ describe("SettingsModal", () => {
|
||||
expect(modal?.getAttribute("style")).toContain("--vv-height: 400px");
|
||||
});
|
||||
|
||||
it("defaults to the global General section when no initialSection is provided", async () => {
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:10:
|
||||
Settings opens on Authentication, not "General · Global". Nothing else in the product works until a provider is connected — the dashboard's own empty state sends operators to Settings for exactly this — whereas the old landing section was app preferences nobody opens Settings to find.
|
||||
Authentication is also not behind the Advanced switch, so the landing section can never be one the operator has hidden; that is asserted here rather than left implicit.
|
||||
*/
|
||||
it("defaults to the Authentication section when no initialSection is provided", async () => {
|
||||
render(
|
||||
<SettingsModal
|
||||
onClose={noop}
|
||||
@@ -292,8 +297,14 @@ describe("SettingsModal", () => {
|
||||
);
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.getByRole("button", { name: /^General · Global$/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:30:
|
||||
Asserts the landing section is REACHABLE, not that it is first in the nav: Authentication leads the AI & Models group (it belongs with the model settings it gates), so nav position is a layout choice that may move again.
|
||||
What must hold is that the section Settings opens on is never one the Advanced switch hides — otherwise a default-configured operator lands on a section their own nav does not list.
|
||||
*/
|
||||
expect(screen.getByRole("checkbox", { name: "Advanced settings" })).not.toBeChecked();
|
||||
expect(screen.getByRole("button", { name: /^Authentication$/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
/*
|
||||
|
||||
@@ -1527,7 +1527,8 @@ describe("SettingsModal", () => {
|
||||
it("falls back to first visible section when initial section is unavailable", async () => {
|
||||
renderModal({ initialSection: "unknown-section" as any });
|
||||
await waitForSettingsModalReady();
|
||||
expect(await screen.findByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
// FNXC:SettingsNavigation 2026-07-16-01:10: an unknown section still falls back to the first visible one; that is Authentication now, which is also where Settings opens.
|
||||
expect(await screen.findByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1661,7 +1661,11 @@ describe("SettingsModal", () => {
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Research Defaults/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:10:
|
||||
The fallback lands on Authentication, which is the first selectable section and where Settings opens. This asserted "General" back when that was the default landing section; the requirement is unchanged — an unreachable section falls back to the first selectable one — only which section that is has moved.
|
||||
*/
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides scheduled evals nav item when experimentalFeatures.evalsView is disabled", async () => {
|
||||
@@ -1698,7 +1702,8 @@ describe("SettingsModal", () => {
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
expect(screen.queryByRole("button", { name: /Scheduled Evals/i })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "General" })).toBeInTheDocument();
|
||||
// FNXC:SettingsNavigation 2026-07-16-01:10: falls back to Authentication — now the first selectable section and where Settings opens. Same requirement, different landing section.
|
||||
expect(screen.getByRole("heading", { name: "Authentication" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -563,8 +563,14 @@ describe("SettingsModal mobile adaptations", () => {
|
||||
const { container, findAllByText } = render(<SettingsModal onClose={vi.fn()} addToast={vi.fn()} />);
|
||||
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
|
||||
|
||||
// Authentication is first by default, so click General to see form controls
|
||||
const generalTabs = await findAllByText("General");
|
||||
/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:10:
|
||||
Settings opens on Authentication, which renders provider cards rather than form
|
||||
controls, so this navigates to a section that has some. The nav label is
|
||||
"General · Project" now that the Global/Project pair is disambiguated — plain
|
||||
"General" no longer matches any element.
|
||||
*/
|
||||
const generalTabs = await findAllByText("General · Project");
|
||||
await user.click(generalTabs[0]);
|
||||
|
||||
const controls = container.querySelectorAll(".settings-content input, .settings-content select, .settings-content textarea");
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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.
|
||||
*/
|
||||
export function CliBinarySection() {
|
||||
const { t } = useTranslation("app");
|
||||
return (<>
|
||||
<h4 className="settings-section-heading">{t("settings.nav.cliBinary", "CLI Binary")}</h4>
|
||||
<CliBinaryPanel />
|
||||
</>);
|
||||
}
|
||||
export default CliBinarySection;
|
||||
@@ -1,5 +1,4 @@
|
||||
import { resolvePersistAgentThinkingLog } from "@fusion/core";
|
||||
import { CliBinaryPanel } from "../../CliBinaryPanel";
|
||||
import { SettingsToggleRow } from "../SettingsToggleRow";
|
||||
import { SettingsSelectRow } from "../SettingsSelectRow";
|
||||
import type { SectionBaseProps } from "./context";
|
||||
@@ -9,7 +8,7 @@ export type GlobalGeneralSectionProps = SectionBaseProps;
|
||||
FNXC:SettingsStyling 2026-07-15-17:35:
|
||||
Plain settings rows render through the shared primitives rather than hand-rolled `form-group` + `checkbox-label` markup, so labels, help copy, and padding come from one type scale. `.form-group` stays global and untouched — 35 non-settings files style forms with it.
|
||||
The migrated keys are all global-tier (DEFAULT_GLOBAL_SETTINGS), so each carries a "global" badge stating that it travels between projects.
|
||||
Rows that stay bespoke are the ones whose copy a single-string descriptor cannot carry without rewording it: the `fn` binary check, the update-check toggle, and the thinking-log group all build label or help from `t()` fragments interleaved with `<code>` tags. The thinking-log pair additionally shares ONE help string across two checkboxes, which no per-row descriptor models. CliBinaryPanel is a custom widget.
|
||||
Rows that stay bespoke are the ones whose copy a single-string descriptor cannot carry without rewording it: the `fn` binary check, the update-check toggle, and the thinking-log group all build label or help from `t()` fragments interleaved with `<code>` tags. The thinking-log pair additionally shares ONE help string across two checkboxes, which no per-row descriptor models. The CLI binary panel has moved to its own advanced-only section.
|
||||
*/
|
||||
export function GlobalGeneralSection({ form, setForm }: GlobalGeneralSectionProps) {
|
||||
const { t } = useTranslation("app");
|
||||
@@ -19,7 +18,10 @@ export function GlobalGeneralSection({ form, setForm }: GlobalGeneralSectionProp
|
||||
FNXC:SourceControl 2026-07-15-20:30:
|
||||
The global GitLab disclosure and the global default tracking repo moved to "Source Control · Global" (SourceControlGlobalSection.tsx), paired with the project source-control section under the Integrations nav group. They are forge integration settings, not general app preferences.
|
||||
*/}
|
||||
<CliBinaryPanel />
|
||||
{/*
|
||||
FNXC:SettingsNavigation 2026-07-16-01:00:
|
||||
The `fn` CLI binary panel moved OUT of this section to its own advanced-only "CLI Binary" section at the bottom of the nav. It used to render first here, so a binary install/version/path panel was the first thing an operator saw on opening Settings — machine plumbing above the preferences they came for. Do not move it back.
|
||||
*/}
|
||||
<SettingsToggleRow
|
||||
descriptor={{
|
||||
key: "dismissModalsOnOutsideClick",
|
||||
|
||||
Reference in New Issue
Block a user