diff --git a/.changeset/fn-7071-concurrency-running-counts.md b/.changeset/fn-7071-concurrency-running-counts.md new file mode 100644 index 0000000000..ad9cf13412 --- /dev/null +++ b/.changeset/fn-7071-concurrency-running-counts.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": minor +--- + +summary: Footer concurrency panel shows running-agent counts and current-use markers on global and project sliders. +category: feature +dev: useGlobalConcurrency now exposes currentlyActive and projectsActive from /api/global-concurrency; EngineControlMenu renders count readouts and a clamped slider-track dot. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 0da413ea01..8ca10baa01 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -994,7 +994,7 @@ 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. 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. The global and current-project concurrency sliders also show how many agents are running 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. ### Engine status banner diff --git a/packages/dashboard/app/components/EngineControlMenu.css b/packages/dashboard/app/components/EngineControlMenu.css index 6738a0c859..2ebc70a665 100644 --- a/packages/dashboard/app/components/EngineControlMenu.css +++ b/packages/dashboard/app/components/EngineControlMenu.css @@ -110,11 +110,34 @@ font-family: var(--font-mono); } +.engine-control-menu__slider-meta { + color: var(--text-muted); + font-size: var(--font-size-xs); +} + +.engine-control-menu__range-wrap { + --engine-control-range-thumb-size: var(--space-md); + position: relative; + display: flex; + align-items: center; +} + .engine-control-menu__range { width: 100%; accent-color: var(--accent); } +/* FNXC:GlobalConcurrencyControls 2026-06-26-06:26: The current-use marker reuses the global .status-dot convention and is positioned with logical inset properties so utilization is visible on LTR/RTL slider tracks without intercepting drag input. */ +.engine-control-menu__use-marker { + --use-pct: 0%; + --use-offset: 0%; + position: absolute; + inset-block-start: 50%; + inset-inline-start: var(--use-offset, var(--use-pct)); + transform: translate(-50%, -50%); + pointer-events: none; +} + .engine-control-menu__error { margin: 0; font-size: var(--font-size-xs); diff --git a/packages/dashboard/app/components/EngineControlMenu.tsx b/packages/dashboard/app/components/EngineControlMenu.tsx index a0b5383c1c..80ef17dcc5 100644 --- a/packages/dashboard/app/components/EngineControlMenu.tsx +++ b/packages/dashboard/app/components/EngineControlMenu.tsx @@ -1,5 +1,5 @@ import "./EngineControlMenu.css"; -import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; +import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState, type CSSProperties } from "react"; import { useTranslation } from "react-i18next"; import { DEFAULT_PROJECT_SETTINGS } from "@fusion/core"; import { Pause, Play, SlidersHorizontal, Square } from "lucide-react"; @@ -54,6 +54,18 @@ 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); +} + +function getUseMarkerStyle(ratio: number): CSSProperties { + return { + "--use-pct": `${ratio * 100}%`, + "--use-offset": `calc((var(--engine-control-range-thumb-size) / 2) + ((100% - var(--engine-control-range-thumb-size)) * ${ratio}))`, + } as CSSProperties; +} + /* FNXC:EngineControls 2026-06-21-00:00: Engine stop/start, triage pause/resume, and live scheduler concurrency/worktree sliders moved from the Header split button into the footer status bar. Operators open this popover from the footer trigger or running-status text, and the sliders reuse the existing /api/settings debounce flow so no backend route is added for live scheduler tuning. @@ -193,6 +205,11 @@ export const EngineControlMenu = forwardRef @@ -253,16 +270,31 @@ export const EngineControlMenu = forwardRef{gc.value} - gc.setValue(event.target.value)} - /> + {globalCountsLoaded ? ( + + {t("commandCenter.controls.concurrency.runningGlobal", "{{count}} running (all projects)", { count: gc.currentlyActive })} + + ) : null} + + gc.setValue(event.target.value)} + /> + {globalCountsLoaded ? ( + {gc.status === "error" ?

{t("commandCenter.controls.concurrency.error", "Unable to load concurrency settings")}

: null} @@ -274,26 +306,45 @@ export const EngineControlMenu = forwardRef + {/** + FNXC:GlobalConcurrencyControls 2026-06-26-06:26: + The footer concurrency panel now shows read-only utilization counts next to the editable caps so operators can compare running agents against limits without opening another dashboard surface. Counts render only after the shared global-concurrency hook is loaded to avoid presenting a stale zero as live truth. + */}