refactor(FN-1370): merge execution settings into scheduling section

- Remove separate Execution section from settings sidebar
- Move step execution settings (runStepsInNewSessions, maxParallelSteps) into Scheduling section
- Consolidate related runtime behavior settings for better UX
- Update tests to navigate to Scheduling section instead of Execution
This commit is contained in:
gsxdsm
2026-04-09 11:08:50 -07:00
parent 81a98c3acf
commit f0619ed678
2 changed files with 42 additions and 53 deletions

View File

@@ -27,7 +27,8 @@ import { applyPresetToSelection, generateUniquePresetId } from "../utils/modelPr
* - models: All model settings — default model (global), planning & validator models,
* model presets, and AI summarization (project). Rendered as sub-sections on one screen.
* - appearance: Theme and color settings (global)
* - scheduling: Concurrency, poll interval, file overlap serialization (project)
* - scheduling: Concurrency, poll interval, file overlap serialization, and step execution
* settings (runStepsInNewSessions, maxParallelSteps) (project)
* - worktrees: Worktree limits, init commands, recycling (project)
* - commands: Test and build command configuration (project)
* - merge: Auto-merge settings (project)
@@ -40,7 +41,6 @@ const SETTINGS_SECTIONS = [
{ id: "appearance", label: "Appearance", scope: "global" as const },
{ id: "scheduling", label: "Scheduling", scope: "project" as const },
{ id: "worktrees", label: "Worktrees", scope: "project" as const },
{ id: "execution", label: "Execution", scope: "project" as const },
{ id: "commands", label: "Commands", scope: "project" as const },
{ id: "merge", label: "Merge", scope: "project" as const },
{ id: "memory", label: "Memory", scope: "project" as const },
@@ -1296,6 +1296,39 @@ export function SettingsModal({
</label>
<small>When enabled, tasks that modify the same files are queued serially to avoid merge conflicts</small>
</div>
<div style={{ borderTop: "1px solid var(--border)", margin: "var(--space-lg) 0" }} />
<h5 className="settings-section-heading">Step Execution</h5>
<div className="form-group">
<label htmlFor="runStepsInNewSessions" className="checkbox-label">
<input
id="runStepsInNewSessions"
type="checkbox"
checked={form.runStepsInNewSessions || false}
onChange={(e) =>
setForm((f) => ({ ...f, runStepsInNewSessions: e.target.checked }))
}
/>
Run each step in a new session
</label>
<small>Run each task step in its own fresh agent session for better isolation and error recovery. Failed steps can be retried individually.</small>
</div>
<div className="form-group">
<label htmlFor="maxParallelSteps">Maximum parallel steps</label>
<input
id="maxParallelSteps"
type="number"
min={1}
max={4}
value={form.maxParallelSteps ?? 2}
onChange={(e) =>
setForm((f) => ({ ...f, maxParallelSteps: Number(e.target.value) }))
}
disabled={!form.runStepsInNewSessions}
/>
<small>Maximum number of steps to run in parallel when file scopes don&apos;t overlap (1-4)</small>
</div>
</>
);
case "worktrees":
@@ -1366,42 +1399,6 @@ export function SettingsModal({
</div>
</>
);
case "execution":
return (
<>
{renderScopeBanner()}
<h4 className="settings-section-heading">Execution</h4>
<div className="form-group">
<label htmlFor="runStepsInNewSessions" className="checkbox-label">
<input
id="runStepsInNewSessions"
type="checkbox"
checked={form.runStepsInNewSessions || false}
onChange={(e) =>
setForm((f) => ({ ...f, runStepsInNewSessions: e.target.checked }))
}
/>
Run each step in a new session
</label>
<small>Run each task step in its own fresh agent session for better isolation and error recovery. Failed steps can be retried individually.</small>
</div>
<div className="form-group">
<label htmlFor="maxParallelSteps">Maximum parallel steps</label>
<input
id="maxParallelSteps"
type="number"
min={1}
max={4}
value={form.maxParallelSteps ?? 2}
onChange={(e) =>
setForm((f) => ({ ...f, maxParallelSteps: Number(e.target.value) }))
}
disabled={!form.runStepsInNewSessions}
/>
<small>Maximum number of steps to run in parallel when file scopes don&apos;t overlap (1-4)</small>
</div>
</>
);
case "commands":
return (
<>

View File

@@ -1276,7 +1276,7 @@ describe("SettingsModal", () => {
const sidebar = container.querySelector(".settings-sidebar");
expect(sidebar).toBeTruthy();
const navItems = sidebar!.querySelectorAll(".settings-nav-item");
expect(navItems.length).toBe(12);
expect(navItems.length).toBe(11);
// Labels include scope emoji indicators (🌐 for global, 📁 for project)
const labels = Array.from(navItems).map((el) => el.textContent);
@@ -1286,7 +1286,6 @@ describe("SettingsModal", () => {
"🌐Appearance",
"📁Scheduling",
"📁Worktrees",
"📁Execution",
"📁Commands",
"📁Merge",
"📁Memory",
@@ -2097,20 +2096,13 @@ describe("SettingsModal", () => {
await waitFor(() => expect(addToast).toHaveBeenCalledWith("Internal server error", "error"));
});
// --- Execution section tests ---
// --- Step Execution field tests (in Scheduling section) ---
it("shows Execution in sidebar", async () => {
it("shows runStepsInNewSessions checkbox and maxParallelSteps input in Scheduling section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
expect(screen.getAllByText("Execution").length).toBeGreaterThanOrEqual(1);
});
it("shows runStepsInNewSessions checkbox and maxParallelSteps input in Execution section", async () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Execution"));
fireEvent.click(screen.getByText("Scheduling"));
const checkbox = screen.getByLabelText("Run each step in a new session");
expect(checkbox).toBeTruthy();
expect(checkbox.getAttribute("type")).toBe("checkbox");
@@ -2124,7 +2116,7 @@ describe("SettingsModal", () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Execution"));
fireEvent.click(screen.getByText("Scheduling"));
const input = screen.getByLabelText("Maximum parallel steps") as HTMLInputElement;
expect(input.disabled).toBe(true);
});
@@ -2133,7 +2125,7 @@ describe("SettingsModal", () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Execution"));
fireEvent.click(screen.getByText("Scheduling"));
const checkbox = screen.getByLabelText("Run each step in a new session");
fireEvent.click(checkbox);
@@ -2145,7 +2137,7 @@ describe("SettingsModal", () => {
render(<SettingsModal onClose={onClose} addToast={addToast} />);
await waitFor(() => expect(fetchSettings).toHaveBeenCalled());
fireEvent.click(screen.getByText("Execution"));
fireEvent.click(screen.getByText("Scheduling"));
const checkbox = screen.getByLabelText("Run each step in a new session");
fireEvent.click(checkbox);