Skip to content

Commit f077678

Browse files
authored
Merge pull request #7147 from jaytbarimbao-collab/fix-governor-pause-reason-reset-7079
fix(miner-ui): reset the governor pause reason after a successful pause
2 parents 7c57d3b + d510f01 commit f077678

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

apps/loopover-miner-ui/src/ledgers.test.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
33

44
import { fetchLedgers, LEDGERS_API_PATH, type LedgersResult, type LedgersSummary } from "./lib/ledgers";
55
import { type GovernorPauseState, type GovernorPauseStateResult } from "./lib/governor";
6-
import { LedgersPage, LedgersView } from "./routes/ledgers";
6+
import { GovernorControlSection, LedgersPage, LedgersView } from "./routes/ledgers";
77
import { handleLedgersRequest, type LedgersApiDeps } from "../vite-ledgers-api";
88

99
// Test-local fixture factories (were lib exports reachable only from tests; moved here per #6187).
@@ -529,3 +529,39 @@ describe("handleLedgersRequest (#4855)", () => {
529529
expect(handled).toEqual({ status: 500, body: JSON.stringify({ error: "sqlite locked" }) });
530530
});
531531
});
532+
533+
describe("GovernorControlSection pause-reason reset (#7079)", () => {
534+
const notPaused: GovernorPauseStateResult = { ok: true, pauseState: { paused: false, reason: null, pausedAt: null } };
535+
const pausedResult: GovernorPauseStateResult = {
536+
ok: true,
537+
pauseState: { paused: true, reason: "investigating flaky test", pausedAt: "2026-07-18T00:00:00.000Z" },
538+
};
539+
const control = (result: GovernorPauseStateResult, onPause = vi.fn()) => (
540+
<GovernorControlSection result={result} pending={false} onPause={onPause} onResume={vi.fn()} />
541+
);
542+
543+
it("clears the reason after a successful pause, so a later resume→pause starts from a blank input", () => {
544+
const onPause = vi.fn();
545+
const { rerender } = render(control(notPaused, onPause));
546+
fireEvent.change(screen.getByLabelText("Pause reason"), { target: { value: "investigating flaky test" } });
547+
fireEvent.click(screen.getByRole("button", { name: "Pause governor" }));
548+
expect(onPause).toHaveBeenCalledWith("investigating flaky test");
549+
550+
// The poll reflects the successful pause (paused: true), then the operator resumes back to the pause form.
551+
rerender(control(pausedResult, onPause));
552+
rerender(control(notPaused, onPause));
553+
expect((screen.getByLabelText("Pause reason") as HTMLInputElement).value).toBe("");
554+
});
555+
556+
it("preserves the typed reason after a FAILED pause, so it can be retried without retyping", () => {
557+
const onPause = vi.fn();
558+
const { rerender } = render(control(notPaused, onPause));
559+
fireEvent.change(screen.getByLabelText("Pause reason"), { target: { value: "investigating flaky test" } });
560+
fireEvent.click(screen.getByRole("button", { name: "Pause governor" }));
561+
562+
// The pause fails (ok: false): the reset must NOT fire. Re-render back to the form (a retry) and the reason stays.
563+
rerender(control({ ok: false, error: "governor state write failed" }, onPause));
564+
rerender(control(notPaused, onPause));
565+
expect((screen.getByLabelText("Pause reason") as HTMLInputElement).value).toBe("investigating flaky test");
566+
});
567+
});

apps/loopover-miner-ui/src/routes/ledgers.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,16 @@ export function GovernorControlSection({
337337
// Optional pause reason, mirroring the CLI's `governor pause [--reason <text>]`; an empty field
338338
// is passed through as `undefined` so it matches the CLI's own optional-flag behavior.
339339
const [reason, setReason] = useState("");
340+
// #7079: once a pause succeeds the polled `result` flips to paused; clear the reason so a later
341+
// resume→pause starts from a blank input. Done with React's "info from previous renders" pattern (a
342+
// render-phase reset when `paused` turns true) rather than an effect. A FAILED pause leaves `result.ok`
343+
// false and never sets `paused`, so the reason is preserved for a retry.
344+
const paused = result?.ok === true && result.pauseState.paused;
345+
const [wasPaused, setWasPaused] = useState(paused);
346+
if (paused !== wasPaused) {
347+
setWasPaused(paused);
348+
if (paused) setReason("");
349+
}
340350
const errorText = result !== null && !result.ok ? result.error : undefined;
341351
return (
342352
<section className="grid gap-3">

0 commit comments

Comments
 (0)