diff --git a/.changeset/fn-8632-concurrency-layout.md b/.changeset/fn-8632-concurrency-layout.md new file mode 100644 index 0000000000..4a985154d7 --- /dev/null +++ b/.changeset/fn-8632-concurrency-layout.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Align Command Center concurrency controls and remove the unused slider column. +category: fix +dev: The two per-project capacity tracks now share a baseline when live running counts are shown. diff --git a/packages/dashboard/app/components/command-center/CommandCenterControls.css b/packages/dashboard/app/components/command-center/CommandCenterControls.css index d8dddf5c85..666d5defd1 100644 --- a/packages/dashboard/app/components/command-center/CommandCenterControls.css +++ b/packages/dashboard/app/components/command-center/CommandCenterControls.css @@ -75,14 +75,22 @@ color: var(--color-error); } +/* +FNXC:CommandCenter 2026-07-31-20:57: +FN-8632 keeps the grid equal to the two per-project capacity sliders that exist. +Stretch each slider and bottom-align its range control so optional running-count captions +cannot desynchronize the range tracks at desktop or tablet widths. +*/ .cc-controls-sliders { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: stretch; gap: var(--space-md); } .cc-controls-slider { display: flex; + align-self: stretch; min-inline-size: 0; flex-direction: column; gap: var(--space-sm); @@ -90,13 +98,6 @@ font-size: 0.8125rem; } -/* FNXC:GlobalConcurrencyControls 2026-06-25-14:10: The global cap is a cross-project setting; span it full-width at the top of the Concurrency card and separate it from the per-project sliders with a divider plus an "Across all projects" caption. */ -.cc-controls-slider--global { - grid-column: 1 / -1; - padding-bottom: var(--space-md); - border-bottom: 1px solid var(--border); -} - .cc-controls-slider-caption { color: var(--text-muted); font-size: 0.75rem; @@ -125,6 +126,11 @@ FN-8007 makes the current-use dot's half-thumb edge inset match the native deskt align-items: center; } +.cc-controls-range-wrap, +.cc-controls-slider > input[type="range"] { + margin-block-start: auto; +} + /* FNXC:GlobalConcurrencyControls 2026-06-26-00:00: The Command Center current-use marker mirrors the footer marker, reuses the status-dot convention, uses logical inline positioning for RTL, and never intercepts range drags. */ .cc-controls-use-marker { --use-pct: 0%; diff --git a/packages/dashboard/app/components/command-center/__tests__/CommandCenterControls.test.tsx b/packages/dashboard/app/components/command-center/__tests__/CommandCenterControls.test.tsx index 2ae1a5ebaf..f7a3885da8 100644 --- a/packages/dashboard/app/components/command-center/__tests__/CommandCenterControls.test.tsx +++ b/packages/dashboard/app/components/command-center/__tests__/CommandCenterControls.test.tsx @@ -1,7 +1,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import { CommandCenterControls } from "../CommandCenterControls"; import { ConfirmDialogProvider } from "../../../hooks/useConfirm"; import { readAppFile } from "../../../test/cssFixture"; @@ -68,6 +68,13 @@ function expectCommandCenterUseOffset(testId: string, ratio: number) { ); } +function cssRule(css: string, selector: string) { + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = css.match(new RegExp(`${escapedSelector}\\s*\\{([\\s\\S]*?)\\}`)); + expect(match, `Expected CSS rule for ${selector}`).not.toBeNull(); + return match?.[1] ?? ""; +} + // FNXC:Theme 2026-07-16-14:30: FN-8146 pins the historical Settings-grid set, including restored shadcn-mono, so a removal from COLOR_THEMES cannot make the all-themes checks pass circularly. const EXPECTED_THEME_IDS = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'air', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'glass-silver', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia', 'cobalt', 'clay', 'moss', 'aurora', 'calm', 'dawn', 'factory-dark', 'shadcn', 'shadcn-ember', 'shadcn-custom', 'shadcn-blue', 'shadcn-green', 'shadcn-red', 'shadcn-purple', 'shadcn-pink', 'shadcn-orange', 'shadcn-yellow', 'shadcn-mono', 'shadcn-mono-red', 'shadcn-mono-blue', 'shadcn-mono-green', 'shadcn-mono-purple', 'shadcn-mono-pink', 'shadcn-mono-orange', 'shadcn-mono-yellow', 'shadcn-black', 'shadcn-gray', 'shadcn-gray-blue'] as const; @@ -120,6 +127,44 @@ describe("CommandCenterControls concurrency markers", () => { + /* + FNXC:CommandCenter 2026-07-31-20:57: + FN-8632 requires the desktop slider grid to contain only the two surviving capacity + controls. Each label stretches and its range control consumes remaining block space + so loaded running-count captions cannot place either track above the shared baseline. + */ + it("keeps concurrency tracks baseline-aligned with and without loaded counts", async () => { + const slidersRule = cssRule(commandCenterControlsCss, ".cc-controls-sliders"); + const sliderRule = cssRule(commandCenterControlsCss, ".cc-controls-slider"); + const rangeBaselineRule = cssRule( + commandCenterControlsCss, + ".cc-controls-range-wrap,\n.cc-controls-slider > input[type=\"range\"]", + ); + + expect(slidersRule).toContain("grid-template-columns: repeat(2, minmax(0, 1fr));"); + expect(slidersRule).toContain("align-items: stretch;"); + expect(sliderRule).toContain("align-self: stretch;"); + expect(rangeBaselineRule).toContain("margin-block-start: auto;"); + expect(commandCenterControlsCss).not.toContain(".cc-controls-slider--global"); + + let resolveCounts!: (value: { currentlyActive: number; queuedCount: number; projectsActive: Record }) => void; + legacyMocks.fetchGlobalConcurrency.mockReturnValue(new Promise((resolve) => { + resolveCounts = resolve; + })); + renderControls(); + expect(screen.getAllByRole("slider")).toHaveLength(2); + expect(screen.queryByTestId("cc-project-running")).not.toBeInTheDocument(); + expect(screen.queryByTestId("cc-global-running")).not.toBeInTheDocument(); + + resolveCounts({ currentlyActive: 10, queuedCount: 0, projectsActive: { proj_123: 10 } }); + await screen.findByTestId("cc-project-running"); + expect(screen.getAllByRole("slider")).toHaveLength(2); + expect(screen.getByTestId("cc-global-running")).toBeInTheDocument(); + for (const slider of screen.getAllByRole("slider")) { + expect(slider.closest(".cc-controls-slider")).toBeTruthy(); + } + }); + it("matches the desktop and mobile native thumb-size CSS contract", () => { expect(commandCenterControlsCss).toContain( "--cc-controls-range-thumb-size: calc(var(--space-lg) + var(--space-xs) / 2);",