feat(settings): simplify section details and layout

This commit is contained in:
gsxdsm
2026-07-10 23:44:45 -07:00
parent 19055c6c0e
commit 9b22927f3c
9 changed files with 195 additions and 20 deletions

View File

@@ -2,6 +2,6 @@
"@runfusion/fusion": minor
---
summary: Add a persistent Advanced settings toggle that keeps uncommon Settings sections hidden by default.
summary: Add a persistent Advanced settings toggle that keeps uncommon Settings sections and controls hidden by default.
category: feature
dev: The browser-local disclosure applies to desktop, mobile, and Settings search without changing saved settings.
dev: The browser-local disclosure applies to navigation, search, and field-level controls without changing saved settings.

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:674d24e02998d9ccdf85ffbc2dc8dc4fa1c070964ef4096b33aed8f8e3d56459
size 189060
oid sha256:b5b6ca940b18f3c99bcd33b5cf5965e0ea2335c6c8c4af145fe96398c7b9e762
size 183359

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b2d0d6d3bddc2f06f178658b6162a088f73154c86b1f3d515728ac0f8169673
size 182222
oid sha256:3acb4caab9fcbd284ac9c2e307b39d0e375e1b2a7b5426eae486d7fd5dc47c27
size 169598

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:429955254d3ee2cac2e0b40016c158e00d37034488b69b2cb0d4411231c3354f
size 258818
oid sha256:3bb749fca2154fcf081dab21c54fbb93280303bc325455392c83b9d16d72502e
size 233320

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9b9441fcaa82f557113c685622b792f1a8c898971b2709f256ba6e5f9c1dd945
size 258732
oid sha256:6ed9dc75baaac936b7476e3cf2fec34914de19d79f5febd44ec4e9f8e8847318
size 233318

View File

@@ -44,6 +44,24 @@
margin-left: auto;
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;
}
.settings-header-actions .settings-github-star-btn__action,
.settings-header-actions .settings-header-discord-btn {
width: 40px;
min-width: 40px;
padding: 0;
justify-content: center;
font-size: 0;
}
.settings-header-actions .settings-github-star-btn__action {
gap: 0;
}
}
/* GitHub star button — split-pill layout: [Star half | Count half] */
@@ -624,8 +642,8 @@ Fix the invariant for BOTH presentations (standalone modal + embedded SettingsVi
}
.settings-navigation {
width: calc(var(--space-xl) * 7 + var(--space-sm));
min-width: calc(var(--space-xl) * 7 + var(--space-sm));
width: calc(var(--space-xl) * 10 + var(--space-sm));
min-width: calc(var(--space-xl) * 10 + var(--space-sm));
border-right: var(--btn-border-width) solid var(--border);
display: flex;
flex-direction: column;
@@ -822,6 +840,72 @@ The Advanced settings preference is a navigation-level disclosure, so keep it vi
color var(--transition-fast),
border-color var(--transition-fast);
}
/*
FNXC:SettingsSimplification 2026-07-11-00:17:
The simplified Settings surface needs one consistent reading rhythm across legacy and typed sections. Use a wider navigation rail to prevent routine labels wrapping, align section content to one gutter, normalize control height/type, and keep helper copy readable without changing the dashboard's existing tokens or font family.
*/
.settings-sidebar {
padding-block: var(--space-md);
}
.settings-nav-item {
min-height: 36px;
display: flex;
align-items: center;
line-height: 1.25;
}
.settings-content {
padding: var(--space-lg) calc(var(--space-xl) + var(--space-sm)) calc(var(--space-xl) + var(--space-md));
}
.settings-content > :is(.settings-scope-banner, .settings-gitlab-disclosure) {
margin-inline: 0;
}
.settings-content .settings-section-heading {
padding-inline: 0;
color: var(--text);
line-height: 1.3;
text-wrap: balance;
}
.settings-content h4.settings-section-heading {
font-size: 0.95rem;
margin-bottom: var(--space-md);
}
.settings-content h5.settings-section-heading {
font-size: 0.82rem;
}
.settings-content .form-group {
padding-inline: 0;
margin-top: var(--space-md);
}
.settings-content .form-group label:not(.checkbox-label) {
font-size: 0.72rem;
line-height: 1.35;
}
.settings-content .checkbox-label {
min-height: 32px;
line-height: 1.35;
}
.settings-content .form-group :is(input:not([type="checkbox"]), select, textarea),
.settings-content :is(.input, .select, .form-input) {
min-height: 38px;
box-sizing: border-box;
}
.settings-content :is(.form-group small, .settings-description, .settings-section-description) {
max-width: 78ch;
line-height: 1.5;
text-wrap: pretty;
}
.settings-nav-item:hover {
background: var(--bg);
color: var(--text);
@@ -862,6 +946,86 @@ The Advanced settings preference is a navigation-level disclosure, so keep it vi
scrollbar-width: thin;
}
/*
FNXC:SettingsSimplification 2026-07-11-00:04:
Basic Settings mode also removes low-frequency controls inside otherwise common sections. Keep the fields mounted so toggling Advanced settings never discards unsaved edits, but remove them from layout and accessibility navigation until the browser-local disclosure is enabled.
*/
.settings-content[data-show-advanced="false"] :is(
.form-group:has(#globalGitlabApiBaseUrl),
.form-group:has(#persistAgentToolOutput),
.form-group:has(#persistAgentThinkingLogPermanent),
.form-group:has(#fnBinaryCheckEnabled),
.form-group:has(#updateCheckFrequency),
.form-group:has(#autoReloadOnVersionChange),
.form-group:has(#taskDetailChatFirst),
.form-group:has(#sessionBannersHidden),
.form-group:has(#failureNotificationDelayMs),
.form-group:has(#webhookFormat),
.form-group:has(#allowAbsoluteFileBrowserPaths),
.form-group:has(#operationalLogRetentionDays),
.form-group:has(#chatRoomRecentVerbatimMessages),
.form-group:has(#chatRoomCompactionFetchLimit),
.form-group:has(#chatRoomSummaryMaxChars),
.form-group:has(#capacityRiskTodoThresholdGeneral),
.form-group:has(#githubTrackingDedupEnabled),
.form-group:has(#gitlabApiBaseUrl),
.form-group:has(#worktreeInitCommand),
.form-group:has(#recycleWorktrees),
.form-group:has(#showWorktreeGrouping),
.form-group:has(#worktree-copy-files-list),
.form-group:has(#executorAllowSiblingBranchRename),
.form-group:has(#worktreeNaming),
.form-group:has(#worktreesDir),
.form-group:has(#worktreeRebaseRemote),
.form-group:has(#worktreeRebaseLocalBase),
.form-group:has(#worktrunkBinaryPath),
.form-group:has(#worktrunkOnFailure),
.form-group:has(#pollIntervalMs),
.form-group:has(#heartbeatScopeDiscipline),
.form-group:has(#taskStuckTimeoutMs),
.form-group:has(#buildTimeoutMs),
.form-group:has(#staleHighFanoutBlockerAgeThresholdMs),
.form-group:has(#preserveProgressOnStuckRequeue),
.form-group:has(#specStalenessEnabled),
.form-group:has(#specStalenessMaxAgeMs),
.form-group:has(#autoArchiveDoneAfterMs),
.form-group:has(#archiveAgentLogMode),
.form-group:has(#autoArchiveDuplicateTasksEnabled),
.form-group:has(#maxStuckKills),
.form-group:has(#ignoreHiddenOverlapPaths),
.form-group:has(.settings-overlap-ignore-row),
.form-group:has(#maxAutoMergeRetries),
#legacy-automerge-stamp-cleanup-panel,
.form-group:has(#mergerMaxReviewPasses),
.form-group:has(#mergerAllowDirtyLocalCheckoutSync),
.form-group:has(#testMode),
.form-group:has(#directMergeCommitStrategy),
.form-group:has(#mergeIntegrationWorktree),
.form-group:has(#mergeAdvanceAutoSync),
.form-group:has(#commitAuthorEnabled),
.form-group:has(#commitAuthorName),
.form-group:has(#commitAuthorEmail),
.form-group:has(#autoResolveConflicts),
.form-group:has(#smartConflictResolution),
.form-group:has(#mergeConflictStrategy),
.form-group:has(#mergeStrategyOverlapBehavior),
.form-group:has(#postMergeAuditMode),
.form-group:has(#pushAfterMerge),
.form-group:has(#pushRemote),
.form-group:has(#project-model-lane-title-summarizer-fallback),
.form-group:has(#autoSelectModelPreset),
.form-group:has(#autoSummarizeTitles),
.form-group:has(#useAiMergeCommitSummary),
.form-group:has(#prTitlePromptInstructions),
.form-group:has(#prDescriptionPromptInstructions),
.model-pricing-section,
details:has(#openrouterAppAttributionReferer),
.form-group:has(#openrouterModelSync),
.form-group:has(#opencodeGoModelSync)
) {
display: none;
}
.settings-content::-webkit-scrollbar {
width: 6px;
}
@@ -2424,7 +2588,7 @@ The header row wraps so the badge drops below the heading on narrow widths inste
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
padding: var(--space-sm) var(--space-sm) var(--space-md);
padding: var(--space-md) var(--space-md) var(--space-lg);
}
.settings-content .form-group small {
@@ -2439,7 +2603,7 @@ The header row wraps so the badge drops below the heading on narrow widths inste
}
.settings-section-heading {
padding: var(--space-md) var(--space-sm) var(--space-sm);
padding: var(--space-md) 0 var(--space-sm);
margin: 0 0 var(--space-sm);
}
@@ -2460,7 +2624,7 @@ The header row wraps so the badge drops below the heading on narrow widths inste
}
.form-group {
padding: 0 var(--space-sm);
padding: 0;
}
.settings-scope-banner {
@@ -2492,7 +2656,7 @@ The header row wraps so the badge drops below the heading on narrow widths inste
}
.settings-description {
padding: 0 var(--space-sm);
padding: 0;
}
.settings-empty-state {

View File

@@ -3895,7 +3895,11 @@ export function SettingsModal({
)}
</nav>
</aside>
<div className="settings-content" ref={settingsContentRef}>
<div
className="settings-content"
ref={settingsContentRef}
data-show-advanced={showAdvancedSettings ? "true" : "false"}
>
{hasSettingsSearchResults ? renderSectionFields() : (
<div className="settings-empty-state settings-search-content-empty" role="status">
<p>{t("settings.search.noResults", "No settings sections match \"{{query}}\".", { query: settingsSearchQuery.trim() })}</p>

View File

@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from "vitest";
import { beforeEach, describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent, waitFor, within, cleanup } from "@testing-library/react";
import path from "path";
import { SettingsModal } from "../SettingsModal";
@@ -238,11 +238,13 @@ describe("SettingsModal", () => {
const toggle = screen.getByRole("checkbox", { name: "Advanced settings" });
expect(toggle).not.toBeChecked();
expect(document.querySelector(".settings-content")).toHaveAttribute("data-show-advanced", "false");
expect(screen.queryByRole("button", { name: /^Node Sync$/ })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /^Experimental Features$/ })).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /^Appearance$/ })).toBeInTheDocument();
await settingsModalUser.click(toggle);
expect(document.querySelector(".settings-content")).toHaveAttribute("data-show-advanced", "true");
expect(localStorage.getItem("fusion:settings:show-advanced")).toBe("true");
expect(screen.getByRole("button", { name: /^Node Sync$/ })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /^Experimental Features$/ })).toBeInTheDocument();
@@ -550,6 +552,7 @@ describe("SettingsModal", () => {
it("persists the legacy sibling branch rename escape hatch in worktree settings", async () => {
renderModal();
await waitForSettingsModalReady();
await settingsModalUser.click(screen.getByRole("checkbox", { name: "Advanced settings" }));
await settingsModalUser.click(screen.getByRole("button", { name: /^Worktrees$/ }));
@@ -617,6 +620,10 @@ describe("SettingsModal", () => {
});
describe("Global General", () => {
beforeEach(() => {
localStorage.setItem("fusion:settings:show-advanced", "true");
});
// Read-only default-render assertions are merged into one rendered
// instance to avoid re-rendering the full modal per pure-display check.
it("renders default global logging fields, helper text, and tracking repo control", async () => {

View File

@@ -55,14 +55,14 @@ export function AppearanceSection({ scopeBanner, form, setForm, themeMode, color
<div className="form-group">
{/* FNXC:TaskDetailActivityFirst 2026-06-30-23:59: The project setting is opt-in because task details now default to Activity-first; explicit Activity/Chat/Logs links keep their destination regardless of this checkbox. */}
<label className="checkbox-label">
<input type="checkbox" checked={form.taskDetailChatFirst === true} onChange={(e) => setForm((f) => ({ ...f, taskDetailChatFirst: e.target.checked }))}/>
<input id="taskDetailChatFirst" type="checkbox" checked={form.taskDetailChatFirst === true} onChange={(e) => setForm((f) => ({ ...f, taskDetailChatFirst: e.target.checked }))}/>
<span>{t("settings.appearance.taskDetailChatFirst", "Open task details with Chat first")}</span>
</label>
<small className="form-text text-muted">{t("settings.appearance.taskDetailChatFirstHelp", "Off by default: task details list Activity first and omitted non-done opens land on Activity. Turn on to restore Chat-first order/default; explicit Chat links still work either way.")}</small>
</div>
<div className="form-group">
<label className="checkbox-label">
<input type="checkbox" checked={sessionBannersHidden} onChange={(e) => setSessionBannersHidden(e.target.checked)}/>
<input id="sessionBannersHidden" type="checkbox" checked={sessionBannersHidden} onChange={(e) => setSessionBannersHidden(e.target.checked)}/>
<span>{t("settings.appearance.hideAISessionNotificationBanners", "Hide AI session notification banners")}</span>
</label>
<small className="form-text text-muted">{t("settings.appearance.suppressTheLdquoNeedsYourInputRdquoBanner", " Suppress the &ldquo;needs your input&rdquo; banner that appears when AI sessions are awaiting input or have failed. ")}</small>