FN-8632: align Command Center concurrency controls
Align Command Center capacity controls so their slider tracks remain visually synchronized. - Use a two-column grid for the surviving per-project capacity sliders. - Stretch slider cards and bottom-align range inputs despite optional running-count captions. - Add regression coverage and a patch changeset for the layout correction. Files changed: .changeset/fn-8632-concurrency-layout.md | 7 ++++ .../command-center/CommandCenterControls.css | 22 ++++++---- .../__tests__/CommandCenterControls.test.tsx | 47 +++++++++++++++++++++- 3 files changed, 67 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-8632 Fusion-Task-Lineage: c59f52fc-0e5b-4633-98fa-64b8a60621d0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8632-concurrency-layout.md
Normal file
7
.changeset/fn-8632-concurrency-layout.md
Normal file
@@ -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.
|
||||
@@ -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%;
|
||||
|
||||
@@ -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<string, number> }) => 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);",
|
||||
|
||||
Reference in New Issue
Block a user