FN-7973: fix mobile concurrency sliders with touch-action none

Restore horizontal concurrency thumb drags on mobile by opting range inputs out of the pan-y ancestor lock.

- Set touch-action:none on Engine Control menu and Command Center concurrency range inputs
- Update geometry/touch contract test to assert none and reject pan-y
- Add patch changeset for the mobile slider fix

Files changed:
 .changeset/fn-7973-mobile-concurrency-sliders.md              |  7 +++++++
 packages/dashboard/app/components/EngineControlMenu.css       |  5 ++++-
 .../app/components/__tests__/EngineControlMenu.test.tsx       | 11 ++++++++---
 .../app/components/command-center/CommandCenterControls.css   |  5 ++++-
 4 files changed, 23 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-7973

Fusion-Task-Lineage: bf36e544-c24f-49ae-beae-b11707be9c79

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-15 14:12:43 -07:00
parent aa0d8635fa
commit 2179a61db9
4 changed files with 23 additions and 5 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Fix concurrency sliders being undraggable on mobile touch devices.
category: fix
dev: The footer Engine Control menu and Command Center Concurrency card range inputs now use touch-action:none so the mobile pan-y ancestor lock no longer hijacks a horizontal thumb drag into page pan.

View File

@@ -141,11 +141,14 @@
/* /*
FNXC:GlobalConcurrencyControls 2026-06-30-21:45: FNXC:GlobalConcurrencyControls 2026-06-30-21:45:
Footer current-use dots must align with the Command Center concurrency sliders. Mirror the dashboard range-input geometry instead of nudging the marker so the shared current/cap marker math lands on the same effective track/thumb positions across footer and dashboard surfaces. Footer current-use dots must align with the Command Center concurrency sliders. Mirror the dashboard range-input geometry instead of nudging the marker so the shared current/cap marker math lands on the same effective track/thumb positions across footer and dashboard surfaces.
FNXC:GlobalConcurrencyControls 2026-07-15-00:00:
FN-7973 requires horizontal concurrency thumb drags to opt out of the mobile pan-y ancestor lock. touch-action:none gives a touch beginning on this range input to the native slider rather than allowing a near-horizontal drag to become page panning.
*/ */
inline-size: 100%; inline-size: 100%;
min-block-size: var(--space-xl); min-block-size: var(--space-xl);
accent-color: var(--accent); accent-color: var(--accent);
touch-action: pan-y; touch-action: none;
} }
/* 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. */ /* 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. */

View File

@@ -115,7 +115,8 @@ describe("EngineControlMenu", () => {
}); });
it("matches the Command Center slider geometry contract for footer current-use markers", () => { // FNXC:GlobalConcurrencyControls 2026-07-15-00:00: FN-7973 requires touch-action:none on both concurrency slider surfaces because the mobile pan-y ancestor lock otherwise steals horizontal native thumb drags.
it("matches the Command Center slider geometry and mobile touch contract for footer current-use markers", () => {
const footerWrap = cssRule(engineControlMenuCss, ".engine-control-menu__range-wrap"); const footerWrap = cssRule(engineControlMenuCss, ".engine-control-menu__range-wrap");
const footerRange = cssRule(engineControlMenuCss, ".engine-control-menu__range"); const footerRange = cssRule(engineControlMenuCss, ".engine-control-menu__range");
const footerMarker = cssRule(engineControlMenuCss, ".engine-control-menu__use-marker"); const footerMarker = cssRule(engineControlMenuCss, ".engine-control-menu__use-marker");
@@ -131,11 +132,13 @@ describe("EngineControlMenu", () => {
expect(footerRange).toContain("inline-size: 100%;"); expect(footerRange).toContain("inline-size: 100%;");
expect(footerRange).toContain("min-block-size: var(--space-xl);"); expect(footerRange).toContain("min-block-size: var(--space-xl);");
expect(footerRange).toContain("accent-color: var(--accent);"); expect(footerRange).toContain("accent-color: var(--accent);");
expect(footerRange).toContain("touch-action: pan-y;"); expect(footerRange).toContain("touch-action: none;");
expect(footerRange).not.toContain("touch-action: pan-y;");
expect(commandTouchSliderRule).toContain("inline-size: 100%;"); expect(commandTouchSliderRule).toContain("inline-size: 100%;");
expect(commandTouchSliderRule).toContain("min-block-size: var(--space-xl);"); expect(commandTouchSliderRule).toContain("min-block-size: var(--space-xl);");
expect(commandTouchSliderRule).toContain("accent-color: var(--accent);"); expect(commandTouchSliderRule).toContain("accent-color: var(--accent);");
expect(commandTouchSliderRule).toContain("touch-action: pan-y;"); expect(commandTouchSliderRule).toContain("touch-action: none;");
expect(commandTouchSliderRule).not.toContain("touch-action: pan-y;");
for (const declaration of ["position: absolute;", "inset-block-start: 50%;", "inset-inline-start: var(--use-offset, var(--use-pct));", "transform: translate(-50%, -50%);", "pointer-events: none;"]) { for (const declaration of ["position: absolute;", "inset-block-start: 50%;", "inset-inline-start: var(--use-offset, var(--use-pct));", "transform: translate(-50%, -50%);", "pointer-events: none;"]) {
expect(footerMarker).toContain(declaration); expect(footerMarker).toContain(declaration);
expect(commandMarker).toContain(declaration); expect(commandMarker).toContain(declaration);
@@ -495,6 +498,8 @@ describe("EngineControlMenu", () => {
await openMenu(); await openMenu();
const loadingGlobalMaxConcurrent = await screen.findByLabelText(/maximum concurrent agents across all projects/i); const loadingGlobalMaxConcurrent = await screen.findByLabelText(/maximum concurrent agents across all projects/i);
// FNXC:GlobalConcurrencyControls 2026-07-15-00:00: FN-7973 restores native touch dragging only for enabled ranges; loading caps remain disabled no-ops.
expect(loadingGlobalMaxConcurrent).toHaveAttribute("disabled");
expect(loadingGlobalMaxConcurrent).toBeDisabled(); expect(loadingGlobalMaxConcurrent).toBeDisabled();
vi.useFakeTimers(); vi.useFakeTimers();

View File

@@ -135,13 +135,16 @@
/* /*
FNXC:CommandCenter 2026-06-20-00:25: FNXC:CommandCenter 2026-06-20-00:25:
Mobile users must be able to drag horizontal concurrency sliders without the page scroll gesture stealing the thumb movement. Scope touch-action to these range inputs and preserve desktop accent/full-width behavior while increasing the hit area with design tokens. Mobile users must be able to drag horizontal concurrency sliders without the page scroll gesture stealing the thumb movement. Scope touch-action to these range inputs and preserve desktop accent/full-width behavior while increasing the hit area with design tokens.
FNXC:CommandCenter 2026-07-15-00:00:
FN-7973 requires touch-action:none because the mobile pan-y ancestor lock otherwise claims near-horizontal thumb drags as page pans. This shared rule covers every Command Center concurrency range input while preserving disabled and loading no-op behavior.
*/ */
.cc-controls-slider input[type="range"], .cc-controls-slider input[type="range"],
.cc-controls-touch-slider { .cc-controls-touch-slider {
inline-size: 100%; inline-size: 100%;
min-block-size: var(--space-xl); min-block-size: var(--space-xl);
accent-color: var(--accent); accent-color: var(--accent);
touch-action: pan-y; touch-action: none;
} }
@media (max-width: 768px) { @media (max-width: 768px) {