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:
@@ -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'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't overlap (1-4)</small>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
case "commands":
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user