From 7b43f73327e4f2ca6815c1e0920c7b0f47138087 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 29 Jun 2026 13:44:09 -0700 Subject: [PATCH] FN-7235: align footer concurrency use markers Align footer concurrency indicators with absolute running-agent utilization. - Change footer use-marker ratio math to use current running count over configured cap. - Cover zero, one-active, mid-track, over-cap, loading, and error marker states in EngineControlMenu tests. - Document the footer marker behavior and add a patch changeset for the published CLI package. Files changed: .changeset/fn-7235-footer-concurrency-marker.md | 7 +++ docs/dashboard-guide.md | 3 +- .../dashboard/app/components/EngineControlMenu.tsx | 14 +++-- .../__tests__/EngineControlMenu.test.tsx | 70 +++++++++++++++++++--- 4 files changed, 79 insertions(+), 15 deletions(-) Fusion-Task-Id: FN-7235 Fusion-Task-Lineage: 0dda1277-2d89-4195-882c-6488dfc67288 Co-authored-by: Fusion (runfusion.ai) --- .../fn-7235-footer-concurrency-marker.md | 7 ++ docs/dashboard-guide.md | 3 +- .../app/components/EngineControlMenu.tsx | 14 ++-- .../__tests__/EngineControlMenu.test.tsx | 70 ++++++++++++++++--- 4 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 .changeset/fn-7235-footer-concurrency-marker.md diff --git a/.changeset/fn-7235-footer-concurrency-marker.md b/.changeset/fn-7235-footer-concurrency-marker.md new file mode 100644 index 0000000000..1cf5ef7688 --- /dev/null +++ b/.changeset/fn-7235-footer-concurrency-marker.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Footer concurrency markers now line up with the running-agent counts. +category: fix +dev: Align EngineControlMenu current-use marker math with CommandCenterControls by mapping utilization as current / cap instead of slider min/max coordinates. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 38f1276844..f75f9cc96a 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1030,7 +1030,8 @@ Use this panel when upgrading a project with pre-FN-6245/FN-6277 in-review rows ### Executor footer engine controls -The global AI engine stop/start control and triage pause/resume control live in the executor footer status bar rather than the header. Select the small engine-controls button beside the executor state badge, or select the state text such as **Running**, to open the footer popover. The popover includes **Stop AI engine** / **Start AI engine**, **Pause triage** / **Resume scheduling**, and live scheduler sliders for max concurrent tasks, max triage concurrency, and max worktrees. Use the visible **Close engine controls** X button, Escape, or outside-click to dismiss it. The global and current-project concurrency sliders also show how many agents are running, including actively-triaging planners (`triage` + `planning`, not paused), and a dot on the slider track for current use, clamped to the track when usage exceeds the configured cap. Slider changes save through the existing `/api/settings` path with the same debounced behavior used by Command Center controls; no separate backend route is required. + +The global AI engine stop/start control and triage pause/resume control live in the executor footer status bar rather than the header. Select the small engine-controls button beside the executor state badge, or select the state text such as **Running**, to open the footer popover. The popover includes **Stop AI engine** / **Start AI engine**, **Pause triage** / **Resume scheduling**, and live scheduler sliders for max concurrent tasks, max triage concurrency, and max worktrees. Use the visible **Close engine controls** X button, Escape, or outside-click to dismiss it. The global and current-project concurrency sliders also show how many agents are running, including actively-triaging planners (`triage` + `planning`, not paused), and a dot on the slider track for current use. The dot uses absolute utilization (`running / cap`) rather than range-slider coordinates, so one running agent renders above the start of the track, zero stays at the start, and over-cap usage clamps to the end. Slider changes save through the existing `/api/settings` path with the same debounced behavior used by Command Center controls; no separate backend route is required. Brief, single-poll executor stats fetch blips keep showing the last good footer stats instead of flashing **Connecting…**. Routine executor stats heartbeats also keep the populated footer mounted after initial load, so an open engine/concurrency popover stays open while counts refresh. The footer only switches to **Connecting…** for sustained suspension-like stats failures, or to an explicit error state for non-transient failures. diff --git a/packages/dashboard/app/components/EngineControlMenu.tsx b/packages/dashboard/app/components/EngineControlMenu.tsx index edba93fe05..b960be6eb6 100644 --- a/packages/dashboard/app/components/EngineControlMenu.tsx +++ b/packages/dashboard/app/components/EngineControlMenu.tsx @@ -54,9 +54,13 @@ function getErrorMessage(error: unknown, fallback: string) { return error instanceof Error ? error.message : fallback; } -function getUseMarkerRatio(current: number, min: number, max: number) { - if (max <= min) return 0; - return clamp((current - min) / (max - min), 0, 1); +/* +FNXC:GlobalConcurrencyControls 2026-06-29-10:30: +FN-7235 keeps the footer current-use marker consistent with FN-7160 Command Center behavior: it shows absolute utilization on a 0..cap scale. Do not subtract the range input floor of 1, because one running agent must render above zero even though the editable slider cannot be set to 0. +*/ +function getUseMarkerRatio(current: number, max: number) { + if (max <= 0) return 0; + return clamp(current / max, 0, 1); } function getUseMarkerStyle(ratio: number): CSSProperties { @@ -244,8 +248,8 @@ export const EngineControlMenu = forwardRef diff --git a/packages/dashboard/app/components/__tests__/EngineControlMenu.test.tsx b/packages/dashboard/app/components/__tests__/EngineControlMenu.test.tsx index 96a2fc08af..5ad0f3f588 100644 --- a/packages/dashboard/app/components/__tests__/EngineControlMenu.test.tsx +++ b/packages/dashboard/app/components/__tests__/EngineControlMenu.test.tsx @@ -54,6 +54,12 @@ function mockGlobalConcurrency(overrides: Partial<{ }); } +// FNXC:EngineControls 2026-06-29-12:00: FN-7235 reproduces the footer mismatch by asserting running-count markers use the loaded cap (`current / cap`) rather than expanded slider-track coordinates; both global and project renderers must move 1 running agent above zero. +// FNXC:EngineControls 2026-06-29-13:25: Keep the footer marker guard aligned with the Command Center representative states: zero, one active, mid-track utilization, over-cap clamping, loading, and error. +function expectUseMarkerPct(testId: string, pct: string) { + expect(screen.getByTestId(testId).style.getPropertyValue("--use-pct")).toBe(pct); +} + describe("EngineControlMenu", () => { beforeEach(() => { vi.useRealTimers(); @@ -218,7 +224,7 @@ describe("EngineControlMenu", () => { expect(screen.getByLabelText(/max worktrees/i)).toHaveAttribute("max", "50"); }); - it("renders running counts and current-use markers with clamped slider positions", async () => { + it("renders running counts and current-use markers with clamped absolute utilization", async () => { legacyMocks.fetchSettings.mockResolvedValue({ ...defaultSettings, maxConcurrent: 50, @@ -237,20 +243,66 @@ describe("EngineControlMenu", () => { expect(screen.getByTestId("engine-control-project-use-marker")).toHaveStyle({ "--use-pct": "100%" }); }); - it("positions current-use markers at zero and mid-track for representative running counts", async () => { + it("positions current-use markers by absolute utilization instead of slider-coordinate math", async () => { + legacyMocks.fetchSettings.mockResolvedValue({ + ...defaultSettings, + maxConcurrent: 50, + }); mockGlobalConcurrency({ - globalMaxConcurrent: 33, + globalMaxConcurrent: 50, currentlyActive: 17, - projectsActive: { proj_123: 0 }, + projectsActive: { proj_123: 17 }, }); await openMenu(); - expect(await screen.findByTestId("engine-control-global-use-marker")).toHaveStyle({ "--use-pct": "50%" }); - expect(screen.getByTestId("engine-control-project-use-marker")).toHaveStyle({ "--use-pct": "0%" }); + await screen.findByTestId("engine-control-global-use-marker"); + expectUseMarkerPct("engine-control-global-use-marker", "34%"); + expectUseMarkerPct("engine-control-project-use-marker", "34%"); + expect(screen.getByTestId("engine-control-global-use-marker").style.getPropertyValue("--use-pct")).not.toBe(`${((17 - 1) / (50 - 1)) * 100}%`); + expect(screen.getByTestId("engine-control-project-use-marker").style.getPropertyValue("--use-pct")).not.toBe(`${((17 - 1) / (50 - 1)) * 100}%`); + expect(screen.queryAllByTestId(/engine-control-.*-use-marker/)).toHaveLength(2); }); - it("defaults the current-project running count to zero for empty projectsActive and missing projectId", async () => { + it("positions mid-track footer markers using absolute utilization", async () => { + legacyMocks.fetchSettings.mockResolvedValue({ + ...defaultSettings, + maxConcurrent: 10, + }); + mockGlobalConcurrency({ + globalMaxConcurrent: 10, + currentlyActive: 6, + projectsActive: { proj_123: 6 }, + }); + + await openMenu(); + + await screen.findByTestId("engine-control-global-use-marker"); + expectUseMarkerPct("engine-control-global-use-marker", "18.75%"); + expectUseMarkerPct("engine-control-project-use-marker", "12%"); + }); + + it("keeps one active agent visibly above zero on both footer markers", async () => { + legacyMocks.fetchSettings.mockResolvedValue({ + ...defaultSettings, + maxConcurrent: 10, + }); + mockGlobalConcurrency({ + globalMaxConcurrent: 10, + currentlyActive: 1, + projectsActive: { proj_123: 1 }, + }); + + await openMenu(); + + await screen.findByTestId("engine-control-global-use-marker"); + expectUseMarkerPct("engine-control-global-use-marker", "3.125%"); + expectUseMarkerPct("engine-control-project-use-marker", "2%"); + expect(screen.getByTestId("engine-control-global-use-marker").style.getPropertyValue("--use-pct")).not.toBe("0%"); + expect(screen.getByTestId("engine-control-project-use-marker").style.getPropertyValue("--use-pct")).not.toBe("0%"); + }); + + it("positions zero running at the start of both footer markers", async () => { mockGlobalConcurrency({ globalMaxConcurrent: 6, currentlyActive: 0, @@ -265,7 +317,7 @@ describe("EngineControlMenu", () => { expect(screen.getByTestId("engine-control-project-use-marker")).toHaveStyle({ "--use-pct": "0%" }); }); - it("does not render running counts or markers while global concurrency is loading", async () => { + it("suppresses footer running counts and markers while utilization is loading", async () => { let resolveGlobalConcurrency!: (value: { globalMaxConcurrent: number; currentlyActive: number; @@ -293,7 +345,7 @@ describe("EngineControlMenu", () => { }); }); - it("does not render running counts or markers when global concurrency fails to load", async () => { + it("suppresses footer running counts and markers when utilization fails", async () => { legacyMocks.fetchGlobalConcurrency.mockRejectedValue(new Error("global concurrency unavailable")); await openMenu();