fix: restore current main regression invariants (#2755)
## Summary - align the renamed-review CLI regression with the classifier’s resolved-boolean contract - keep GitHub tracking controls expanded across same-task detail and sparse SSE updates - strengthen the sticky tracking regression to wait for the sparse update ## Test plan - `pnpm --filter @runfusion/fusion exec vitest run src/__tests__/cli-active-count-lanes.test.ts` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/restart-recovery-coordinator.test.ts` - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/TaskDetailModal.inline-editing-and-integrations.test.tsx` - `pnpm --filter @fusion/dashboard typecheck` - `pnpm --filter @runfusion/fusion typecheck` - `pnpm check:changesets`
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@fusion/dashboard": patch
|
||||
---
|
||||
|
||||
summary: GitHub tracking controls now stay expanded while same-task detail updates preserve disabled tracking state.
|
||||
category: fix
|
||||
dev: Reset GitHub tracking disclosure state only when the selected task id changes, and align the renamed-review CLI regression with the classifier's resolved-boolean contract.
|
||||
@@ -131,28 +131,28 @@ and then delegated to a predicate that did not: the card was refused for the ONE
|
||||
allow, and no message anywhere mentions columns.
|
||||
|
||||
Testing the classifier directly rather than through three route harnesses: it is a pure function and the
|
||||
defect lives in it. The three call sites passing their sets is asserted structurally below, because a call
|
||||
defect lives in it. The three call sites passing their resolved answers are asserted structurally below, because a call
|
||||
site that accepts the parameter and does not pass it is the failure mode a unit test cannot see.
|
||||
*/
|
||||
describe("the shared missing-worktree classifier takes the caller's review lane", () => {
|
||||
describe("the shared missing-worktree classifier takes the caller's resolved review answer", () => {
|
||||
const FAILURE = "Refusing to start coding agent in missing worktree: /gone";
|
||||
|
||||
it("recognises a renamed review lane when the caller supplies it", async () => {
|
||||
it("recognises a renamed review lane when the caller resolves it", async () => {
|
||||
const { isInReviewMissingWorktreeSessionStartFailure } = await import("@fusion/engine");
|
||||
const task = { id: "FN-1", column: "signoff", error: FAILURE } as never;
|
||||
|
||||
// Pre-fix: `signoff` !== "in-review", so the retry bypass this classifier exists for never applied.
|
||||
expect(isInReviewMissingWorktreeSessionStartFailure(task, new Set(["signoff"]))).toBe(true);
|
||||
expect(isInReviewMissingWorktreeSessionStartFailure(task, true)).toBe(true);
|
||||
});
|
||||
|
||||
it("still refuses a column outside the supplied set", async () => {
|
||||
it("still refuses a column the caller did not resolve as review", async () => {
|
||||
const { isInReviewMissingWorktreeSessionStartFailure } = await import("@fusion/engine");
|
||||
const task = { id: "FN-2", column: "building", error: FAILURE } as never;
|
||||
|
||||
expect(isInReviewMissingWorktreeSessionStartFailure(task, new Set(["signoff"]))).toBe(false);
|
||||
expect(isInReviewMissingWorktreeSessionStartFailure(task, false)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the legacy literal when no set is supplied", async () => {
|
||||
it("keeps the legacy literal when no resolved answer is supplied", async () => {
|
||||
// Backwards compatibility is the reason the parameter is optional: existing callers must not change.
|
||||
const { isInReviewMissingWorktreeSessionStartFailure } = await import("@fusion/engine");
|
||||
|
||||
@@ -160,7 +160,7 @@ describe("the shared missing-worktree classifier takes the caller's review lane"
|
||||
expect(isInReviewMissingWorktreeSessionStartFailure({ id: "FN-4", column: "signoff", error: FAILURE } as never)).toBe(false);
|
||||
});
|
||||
|
||||
it("is called WITH a resolved set at all three surfaces", async () => {
|
||||
it("is called WITH a resolved review answer at all three surfaces", async () => {
|
||||
/*
|
||||
A call site that accepts the parameter and forgets to pass it is exactly the half-conversion this thread
|
||||
was about, and no unit test on the classifier can see it. Structural, and it names the file so a fourth
|
||||
@@ -177,7 +177,7 @@ describe("the shared missing-worktree classifier takes the caller's review lane"
|
||||
const code = (await readFile(surface, "utf8")).replace(/\/\*[\s\S]*?\*\//g, "");
|
||||
const call = code.match(/isInReviewMissingWorktreeSessionStartFailure\(([^)]*)\)/);
|
||||
expect(call, `${surface.pathname} does not call the classifier`).toBeTruthy();
|
||||
expect(call?.[1], `${surface.pathname} calls it without a resolved review set`).toContain(",");
|
||||
expect(call?.[1], `${surface.pathname} calls it without a resolved review answer`).toContain(",");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1446,13 +1446,18 @@ export function TaskDetailContent({
|
||||
setEditSourceIssueUrl(task.sourceIssue?.url ?? "");
|
||||
setEditExecutionMode(normalizeExecutionModeValue(task.executionMode));
|
||||
setSourceIssueExpanded(false);
|
||||
setGithubTrackingExpanded(false);
|
||||
setGithubRepoOverrideDraft(workingTask.githubTracking?.repoOverride ?? "");
|
||||
setGithubTrackingEnabledDraft(null);
|
||||
setGithubRepoOverrideError(null);
|
||||
setIsEditing(false);
|
||||
}, [task.id, task.title, task.description, task.branch, task.baseBranch, task.sourceIssue, task.executionMode, workingTask.githubTracking]);
|
||||
|
||||
// Disclosure state belongs to the selected task, not to same-task detail
|
||||
// refreshes such as GitHub tracking updates or sparse SSE payloads.
|
||||
useEffect(() => {
|
||||
setGithubTrackingExpanded(false);
|
||||
}, [task.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setWorkflowEnabledSteps(task.enabledWorkflowSteps);
|
||||
}, [task.id, task.enabledWorkflowSteps]);
|
||||
|
||||
@@ -3221,6 +3221,11 @@ describe("TaskDetailModal", () => {
|
||||
},
|
||||
} as Task);
|
||||
|
||||
let signalSparseUpdate!: () => void;
|
||||
const sparseUpdateApplied = new Promise<void>((resolve) => {
|
||||
signalSparseUpdate = resolve;
|
||||
});
|
||||
|
||||
function Harness(): JSX.Element {
|
||||
const [taskState, setTaskState] = useState(baseTask);
|
||||
|
||||
@@ -3240,6 +3245,7 @@ describe("TaskDetailModal", () => {
|
||||
...current,
|
||||
githubTracking: undefined,
|
||||
}));
|
||||
signalSparseUpdate();
|
||||
}, 0);
|
||||
}}
|
||||
addToast={noop}
|
||||
@@ -3259,6 +3265,8 @@ describe("TaskDetailModal", () => {
|
||||
expect(mockUpdate).toHaveBeenCalledWith("FN-001", { githubTracking: { enabled: false } }, undefined);
|
||||
});
|
||||
|
||||
await sparseUpdateApplied;
|
||||
|
||||
await waitFor(() => {
|
||||
expect((screen.getByRole("checkbox", { name: "Enable GitHub tracking" }) as HTMLInputElement).checked).toBe(false);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user