FN-6983: remove branch reattachment warning
Remove the stale branch reattachment affordance from Task Detail while preserving self-healing as the recovery path. - Remove unused rebind banner styles and mobile action-shell rules. - Drop stale recoverBranchBinding mocks from TaskDetailModal tests. - Add rendering coverage proving Task Detail and embedded content do not show reattachment affordances across missing, populated, workspace, and mobile branch states. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-6983-task-detail-rebind-warning.md | 7 ++ .../dashboard/app/components/TaskDetailModal.css | 42 +----------- .../TaskDetailModal.create-pr-e2e.test.tsx | 1 - .../TaskDetailModal.create-pr-integration.test.tsx | 1 - .../__tests__/TaskDetailModal.rendering.test.tsx | 80 ++++++++++++++++++++++ .../__tests__/TaskDetailModal.test-helpers.ts | 1 - 6 files changed, 88 insertions(+), 44 deletions(-) Fusion-Task-Id: FN-6983 Fusion-Task-Lineage: 1a45c38e-f7d0-4df7-94cf-7d1309758e0d
This commit is contained in:
7
.changeset/fn-6983-task-detail-rebind-warning.md
Normal file
7
.changeset/fn-6983-task-detail-rebind-warning.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Stop showing branch reattachment warnings in Task Detail.
|
||||
category: fix
|
||||
dev: Removes stale TaskDetailModal rebind-banner CSS/mocks and covers missing-branch workspace shapes.
|
||||
@@ -2271,48 +2271,8 @@ Narrow mobile task detail surfaces from both Board and List must allow horizonta
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.rebind-banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-lg);
|
||||
border-radius: var(--radius-md);
|
||||
background: color-mix(in srgb, var(--color-warning) 8%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--color-warning) 30%, transparent);
|
||||
}
|
||||
|
||||
.rebind-banner-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.rebind-banner-headline {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.rebind-banner-copy {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.rebind-banner-result {
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.rebind-banner-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.detail-near-duplicate-banner__actions,
|
||||
.rebind-banner-actions {
|
||||
.detail-near-duplicate-banner__actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
pauseTask: vi.fn().mockResolvedValue({}),
|
||||
unpauseTask: vi.fn().mockResolvedValue({}),
|
||||
recoverBranchBinding: vi.fn(),
|
||||
fetchWorkflowResults: vi.fn().mockResolvedValue([]),
|
||||
fetchTaskReview: vi.fn().mockResolvedValue({ reviewState: { source: "reviewer-agent", items: [], addressing: [] }, automationStatus: null, emptyMessage: "No reviewer feedback yet" }),
|
||||
refreshTaskReview: vi.fn().mockResolvedValue({ reviewState: undefined, automationStatus: null }),
|
||||
|
||||
@@ -37,7 +37,6 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
pauseTask: vi.fn().mockResolvedValue({}),
|
||||
unpauseTask: vi.fn().mockResolvedValue({}),
|
||||
recoverBranchBinding: vi.fn(),
|
||||
fetchWorkflowResults: vi.fn().mockResolvedValue([]),
|
||||
fetchTaskReview: vi.fn().mockResolvedValue({ reviewState: { source: "reviewer-agent", items: [], addressing: [] }, automationStatus: null, emptyMessage: "No reviewer feedback yet" }),
|
||||
refreshTaskReview: vi.fn().mockResolvedValue({ reviewState: undefined, automationStatus: null }),
|
||||
|
||||
@@ -742,6 +742,86 @@ describe("TaskDetailModal", () => {
|
||||
expect(onRequestClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
function expectNoBranchReattachmentAffordance(container: HTMLElement): void {
|
||||
// FN-6983: Task Detail must not ask users to manually reattach branches; self-healing owns recovery.
|
||||
expect(screen.queryByText(/Branch needs reattachment/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Branch binding lost/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/isn't currently attached to a fusion branch/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/Reattached branch/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /Reattach branch/i })).not.toBeInTheDocument();
|
||||
expect(container.querySelector(".rebind-banner")).toBeNull();
|
||||
expect(container.querySelector(".rebind-banner-actions")).toBeNull();
|
||||
expect(container.querySelector(".rebind-banner-result")).toBeNull();
|
||||
}
|
||||
|
||||
function renderTaskDetail(task: ReturnType<typeof makeTask>, mobileHeaderMode?: "back") {
|
||||
return render(
|
||||
<TaskDetailModal
|
||||
initialTab="definition"
|
||||
task={task}
|
||||
onClose={noop}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
mobileHeaderMode={mobileHeaderMode}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("branch reattachment affordance absence", () => {
|
||||
it.each([
|
||||
["null branch and worktree", makeTask({ column: "in-review", branch: null, worktree: null })],
|
||||
["undefined branch with missing worktree", makeTask({ column: "in-review", branch: undefined, worktree: null })],
|
||||
["populated branch control", makeTask({ column: "in-review", branch: "fusion/fn-099", worktree: "/tmp/fn-099" })],
|
||||
["non-in-review missing branch", makeTask({ column: "todo", branch: null, worktree: null })],
|
||||
] as const)("renders no reattachment banner for %s", (_label, task) => {
|
||||
const { container } = renderTaskDetail(task);
|
||||
|
||||
expectNoBranchReattachmentAffordance(container);
|
||||
});
|
||||
|
||||
it("renders no reattachment banner for workspace in-review tasks with no singular branch", () => {
|
||||
const task = makeTask({
|
||||
column: "in-review",
|
||||
worktree: null,
|
||||
workspaceWorktrees: {
|
||||
"repo-a": { worktreePath: "/tmp/fn-099/repo-a", branch: "fusion/fn-099", baseCommitSha: "abc123" },
|
||||
"repo-b": { worktreePath: "/tmp/fn-099/repo-b", branch: "fusion/fn-099" },
|
||||
},
|
||||
});
|
||||
delete (task as { branch?: string | null }).branch;
|
||||
|
||||
const { container } = renderTaskDetail(task);
|
||||
|
||||
expectNoBranchReattachmentAffordance(container);
|
||||
});
|
||||
|
||||
it("keeps the removed mobile rebind action shell absent in narrow task detail rendering", () => {
|
||||
const { container } = renderTaskDetail(makeTask({ column: "in-review", branch: null, worktree: null }), "back");
|
||||
|
||||
expect(screen.getByRole("button", { name: "Back to task list" })).toBeInTheDocument();
|
||||
expectNoBranchReattachmentAffordance(container);
|
||||
});
|
||||
|
||||
it("renders no reattachment banner from embedded TaskDetailContent", () => {
|
||||
const { container } = render(
|
||||
<TaskDetailContent
|
||||
task={makeTask({ column: "in-review", branch: null, worktree: null })}
|
||||
onMoveTask={noopMove}
|
||||
onDeleteTask={noopDelete}
|
||||
onMergeTask={noopMerge}
|
||||
onOpenDetail={noopOpenDetail}
|
||||
addToast={noop}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
|
||||
expectNoBranchReattachmentAffordance(container);
|
||||
});
|
||||
});
|
||||
|
||||
it("styles detail-body scrollbar rules", () => {
|
||||
const css = readDashboardStylesSource();
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ vi.mock("../../api", async (importOriginal) => {
|
||||
updateGlobalSettings: vi.fn().mockResolvedValue({}),
|
||||
pauseTask: vi.fn().mockResolvedValue({}),
|
||||
unpauseTask: vi.fn().mockResolvedValue({}),
|
||||
recoverBranchBinding: vi.fn(),
|
||||
refreshPrStatus: vi.fn(),
|
||||
fetchWorkflowResults: vi.fn().mockResolvedValue([]),
|
||||
fetchTaskReview: vi.fn().mockResolvedValue({ reviewState: { source: "reviewer-agent", items: [], addressing: [] }, automationStatus: null, emptyMessage: "No reviewer feedback yet — this task has not produced reviewer-agent feedback in direct mode." }),
|
||||
|
||||
Reference in New Issue
Block a user