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:
Phil Larson
2026-07-30 06:24:57 -07:00
committed by GitHub
parent 3da8b90ed9
commit 8e50967279
4 changed files with 30 additions and 10 deletions

View File

@@ -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.

View File

@@ -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(",");
}
});
});

View File

@@ -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]);

View File

@@ -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);
});