Skip to content

Commit f75e0d9

Browse files
committed
fix(observatory): bound the concurrency cap and clarify the lower control
Fold #1418 kilo review: - The lower button no longer doubles as a clear: it floors at 1 and disables there, so its 'Lower' label is never misleading. Removal stays the explicit Clear button. - Clamp the cap to a ceiling (MAX_CAP 50) and disable Raise at the top, so the stepper cannot post runaway values; coerceCap also clamps an out-of-range server value. Tests updated for the Clear-button path and the new ceiling.
1 parent 1d9605e commit f75e0d9

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

desktop/src/apps/ObservatoryApp.test.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe("ObservatoryApp", () => {
135135
});
136136
});
137137

138-
it("clears the cap to null from the lowest step", async () => {
138+
it("clears the cap to null via the Clear button", async () => {
139139
const fetchMock = mockFetch({
140140
"GET /api/observatory/fleet": { ok: true, body: fleetBody },
141141
"GET /api/observatory/throttle": { ok: true, body: { global: 1, lanes: {} } },
@@ -148,7 +148,12 @@ describe("ObservatoryApp", () => {
148148
expect(screen.getByLabelText(/concurrency cap value/i).textContent).toBe("1"),
149149
);
150150

151-
fireEvent.click(screen.getByRole("button", { name: /lower concurrency cap/i }));
151+
// The lower button floors at 1 (disabled there); removal is the explicit Clear.
152+
expect(
153+
(screen.getByRole("button", { name: /lower concurrency cap/i }) as HTMLButtonElement)
154+
.disabled,
155+
).toBe(true);
156+
fireEvent.click(screen.getByRole("button", { name: /^clear$/i }));
152157
await flush();
153158

154159
const post = fetchMock.mock.calls.find(
@@ -160,6 +165,25 @@ describe("ObservatoryApp", () => {
160165
});
161166
});
162167

168+
it("disables raising the cap at the ceiling", async () => {
169+
vi.stubGlobal(
170+
"fetch",
171+
mockFetch({
172+
"GET /api/observatory/fleet": { ok: true, body: fleetBody },
173+
"GET /api/observatory/throttle": { ok: true, body: { global: 50, lanes: {} } },
174+
}),
175+
);
176+
render(<ObservatoryApp windowId="w1" />);
177+
await flush();
178+
await waitFor(() =>
179+
expect(screen.getByLabelText(/concurrency cap value/i).textContent).toBe("50"),
180+
);
181+
expect(
182+
(screen.getByRole("button", { name: /raise concurrency cap/i }) as HTMLButtonElement)
183+
.disabled,
184+
).toBe(true);
185+
});
186+
163187
it("shows the idle empty state when no agents are working", async () => {
164188
vi.stubGlobal(
165189
"fetch",

desktop/src/apps/ObservatoryApp.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@ const EMPTY_PAUSE: PauseState = { global: false, lanes: {} };
2424
// Global concurrency cap: how many cards the fleet may hold in flight at once
2525
// (the dispatch loop reads it as MAX_OPEN_PRS). null = no override, the loop
2626
// default applies. Pause is the on/off switch; this is the volume knob.
27+
const MAX_CAP = 50; // sane ceiling so the stepper cannot post runaway values
2728
function coerceCap(v: unknown): number | null {
28-
return typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : null;
29+
if (typeof v !== "number" || !Number.isFinite(v) || v <= 0) return null;
30+
return Math.min(MAX_CAP, Math.floor(v));
2931
}
3032

3133
export function ObservatoryApp({ windowId: _windowId }: { windowId: string }) {
@@ -156,8 +158,8 @@ export function ObservatoryApp({ windowId: _windowId }: { windowId: string }) {
156158
<div className="flex items-center gap-1.5">
157159
<button
158160
type="button"
159-
onClick={() => setGlobalCap(cap && cap > 1 ? cap - 1 : null)}
160-
disabled={busy === "cap" || cap == null}
161+
onClick={() => cap != null && cap > 1 && setGlobalCap(cap - 1)}
162+
disabled={busy === "cap" || cap == null || cap <= 1}
161163
aria-label="Lower concurrency cap"
162164
className="flex h-7 w-7 items-center justify-center rounded-md border border-shell-border text-shell-text-secondary transition-colors hover:text-shell-text hover:border-shell-border-strong disabled:cursor-not-allowed disabled:opacity-40"
163165
>
@@ -171,8 +173,8 @@ export function ObservatoryApp({ windowId: _windowId }: { windowId: string }) {
171173
</span>
172174
<button
173175
type="button"
174-
onClick={() => setGlobalCap((cap ?? 0) + 1)}
175-
disabled={busy === "cap"}
176+
onClick={() => setGlobalCap(Math.min(MAX_CAP, (cap ?? 0) + 1))}
177+
disabled={busy === "cap" || (cap != null && cap >= MAX_CAP)}
176178
aria-label="Raise concurrency cap"
177179
className="flex h-7 w-7 items-center justify-center rounded-md border border-shell-border text-shell-text-secondary transition-colors hover:text-shell-text hover:border-shell-border-strong disabled:cursor-not-allowed disabled:opacity-40"
178180
>

0 commit comments

Comments
 (0)