Files
fusion/packages/dashboard/app/components/command-center/__tests__/CommandCenterControls.test.tsx
gsxdsm 18d654a5ff capacity, part 3: delete the globalMaxConcurrent setting, API and UI (#2529)
Part 3 of the capacity simplification, and the half that removes the
**knob**. Enforcement (shared semaphore, runtime wiring) went in #2509;
this removes everything an operator or API client can still see, so
nothing is left readable-but-ignored.

## Deleted

Settings key + schema default · CentralCore’s
`getGlobalConcurrencyState` / `updateGlobalConcurrency` /
`acquireGlobalSlot` / `releaseGlobalSlot` and the `concurrency:changed`
event · the whole Global Concurrency block in `async-central-core` ·
`PUT /api/global-concurrency` · the Scheduling · Global settings section
· the footer and Command Center global sliders · the dead
`getGlobalConcurrencyLimit` reader whose only caller went in #2509.

## Kept, deliberately

**`GET /api/global-concurrency` survives as telemetry only** — live
`currentlyActive` / `projectsActive` from CentralCore’s side-effect-safe
source. “How busy is this machine?” is still a real question once the
cap that used to answer it is gone. It no longer reports
`globalMaxConcurrent`/`queuedCount`: those came from the deleted cap and
from slot bookkeeping production code never incremented, so publishing
them was publishing zeros dressed as state.

**`useGlobalConcurrency` becomes read-only.** Everything that existed to
*persist* went with the cap — the 500 ms debounce, the save-state
machine, the commit-on-close/unmount flush, the slider clamp, the
`interactive` gate. The module-level shared store is **kept**: its
original justification (two mounted consumers drift apart with private
copies) holds for a polled read exactly as it did for a cap, and one
fetch now serves both.

The live “N running (all projects)” readout survives in both surfaces,
moved onto the per-project row.

## Two sections become one

Scheduling · Global existed to host exactly one control. With it deleted
the section renders an empty pane, so the Global/Project pair merges
back into **“Scheduling”**. An empty nav entry is a promise of settings
that are not there.

## One real fix found on the way

`SchedulingSection`’s `concurrencyLoading` gated the **project**
concurrency inputs on the **global**-concurrency fetch — never the right
source, since `maxConcurrent` and `maxWorktrees` come from the settings
form. It is repointed at the form’s own load, preserving the invariant
it existed for: a concurrency input stays disabled until its live value
arrives, so an operator cannot overwrite a resolved limit with a blank
fallback.

## Migration

A stored `globalMaxConcurrent` is **ignored** — it is a project-blob key
nothing reads, so dropping it needs no schema change. The
`central.global_concurrency` **table** is dropped in a follow-up; this
slice stops seeding and reading it first, so that drop has no live
writer to race.

## Verification, and how the wider suite was controlled

`pnpm lint` clean · core/engine/dashboard `tsc` clean · `pnpm test:gate`
green (309 + 10 + 71) · dashboard settings/footer/command-center/hooks
**2237/2237** · core `central-core-backend` 9/9.

The broader dashboard suite shows failures, and I checked rather than
assumed: running the suspect files on **clean main** reproduces
`api-git` (49), `TaskDetailModal.rendering` (28) and `settings-mobile`
(17) identically. Two were genuinely mine —
`SettingsModal.scheduling-merge` (0 on main, 17 on this branch: my nav
rename) and one `settings-mobile` picker case asserting `scheduling` is
a scoped pair — and both are fixed.

Tests for deleted behaviour are removed with it (footer
confirm/cancel/flush/dedupe, global marker geometry, the hook’s PUT
case, the CentralCore slot cases), each carrying a note on what it
guarded and where the surviving **project-side** equivalent lives.
Fixture-only references were updated, not deleted.

Nothing booted.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 23:23:13 -07:00

151 lines
7.1 KiB
TypeScript

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 { CommandCenterControls } from "../CommandCenterControls";
import { ConfirmDialogProvider } from "../../../hooks/useConfirm";
import { readAppFile } from "../../../test/cssFixture";
const commandCenterControlsCss = readAppFile("components/command-center/CommandCenterControls.css");
const legacyMocks = vi.hoisted(() => ({
fetchConfig: vi.fn(),
fetchSettings: vi.fn(),
updateSettings: vi.fn(),
fetchGlobalConcurrency: vi.fn(),
updateGlobalConcurrency: vi.fn(),
}));
vi.mock("../../../api/legacy", () => legacyMocks);
vi.mock("../../../hooks/useAppSettings", () => ({
useAppSettings: () => ({
globalPaused: false,
toggleGlobalPause: vi.fn(),
refresh: vi.fn().mockResolvedValue(undefined),
}),
}));
const defaultSettings = {
maxConcurrent: 12,
maxWorktrees: 4,
};
function renderControls(projectId = "proj_123") {
const onColorThemeChange = vi.fn();
render(
<ConfirmDialogProvider>
<CommandCenterControls
projectId={projectId}
colorTheme="default"
themeMode="dark"
onColorThemeChange={onColorThemeChange}
onThemeModeChange={vi.fn()}
/>
</ConfirmDialogProvider>,
);
return { onColorThemeChange };
}
function mockGlobalConcurrency(overrides: Partial<{
currentlyActive: number;
projectsActive: Record<string, number>;
}> = {}) {
legacyMocks.fetchGlobalConcurrency.mockResolvedValue({
currentlyActive: 10,
queuedCount: 0,
projectsActive: { proj_123: 10 },
...overrides,
});
}
function expectUseMarkerPct(testId: string, pct: string) {
expect(screen.getByTestId(testId).style.getPropertyValue("--use-pct")).toBe(pct);
}
function expectCommandCenterUseOffset(testId: string, ratio: number) {
expect(screen.getByTestId(testId).style.getPropertyValue("--use-offset")).toBe(
`calc((var(--cc-controls-range-thumb-size) / 2) + ((100% - var(--cc-controls-range-thumb-size)) * ${ratio}))`,
);
}
// 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', '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;
function renderedThemeIds(listbox: HTMLElement) {
return within(listbox).getAllByRole("option").map((option) => {
const swatch = option.querySelector<HTMLElement>(".theme-option-swatch");
expect(swatch).toBeTruthy();
return [...(swatch?.classList ?? [])].find((className) => className.startsWith("theme-swatch-"))?.replace("theme-swatch-", "");
});
}
describe("CommandCenterControls concurrency markers", () => {
beforeEach(() => {
legacyMocks.fetchConfig.mockResolvedValue({ maxConcurrent: 12, rootDir: "/workspace/project" });
legacyMocks.fetchSettings.mockResolvedValue({ ...defaultSettings });
legacyMocks.updateSettings.mockResolvedValue({ ...defaultSettings });
legacyMocks.updateGlobalConcurrency.mockResolvedValue({});
mockGlobalConcurrency();
});
afterEach(() => {
vi.clearAllMocks();
document.body.innerHTML = "";
});
it("keeps organization portability out of Overview controls", () => {
renderControls();
expect(screen.queryByTestId("cc-controls-org-portability")).not.toBeInTheDocument();
});
it("keeps the Theme card's compact dropdown interactive", () => {
const { onColorThemeChange } = renderControls();
const themeCard = screen.getByTestId("cc-controls-theme");
const trigger = screen.getByRole("button", { name: "Fusion Legacy" });
expect(themeCard).toContainElement(trigger);
fireEvent.click(trigger);
const listbox = screen.getByRole("listbox", { name: "Color theme" });
expect(renderedThemeIds(listbox)).toEqual(EXPECTED_THEME_IDS);
fireEvent.change(screen.getByRole("searchbox", { name: /filter color themes/i }), { target: { value: "dawn" } });
fireEvent.click(screen.getByRole("option", { name: "Dawn" }));
expect(onColorThemeChange).toHaveBeenCalledWith("dawn");
expect(screen.queryByRole("listbox")).not.toBeInTheDocument();
});
// FNXC:GlobalConcurrencyControls 2026-07-15-12:00: FN-8007 requires dashboard markers to use the exact native-thumb coordinate system when the expanded range max exceeds the persisted cap.
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);",
);
// FNXC:GlobalConcurrencyControls 2026-07-15-18:10: FN-8007 keeps desktop browser thumb travel deterministic by sizing both pseudo-thumb implementations from the marker inset token.
for (const selector of [
".cc-controls-slider input[type=\"range\"]::-webkit-slider-thumb,\n.cc-controls-touch-slider::-webkit-slider-thumb",
".cc-controls-slider input[type=\"range\"]::-moz-range-thumb,\n.cc-controls-touch-slider::-moz-range-thumb",
]) {
expect(commandCenterControlsCss).toContain(selector);
}
expect(commandCenterControlsCss).toContain("width: var(--cc-controls-range-thumb-size);");
expect(commandCenterControlsCss).toContain("height: var(--cc-controls-range-thumb-size);");
expect(commandCenterControlsCss).toContain("@media (max-width: 768px)");
expect(commandCenterControlsCss).toContain("--cc-controls-range-thumb-size: var(--space-xl);");
});
/*
FNXC:CapacityModel 2026-07-29-00:25 (drop the cross-project cap — settings half):
The Command Center global-cap marker tests are DELETED with the slider they
measured. They asserted thumb-alignment and over-cap pinning for a control that
wrote a machine-wide limit; the limit is gone (capacity is two numbers PER PROJECT)
and the PUT route with it. The equivalent PROJECT-slider marker cases are retained.
The live "N running (all projects)" readout survives and moved onto the project
row — it is telemetry, so it has no cap to align a marker against.
*/
});