feat(FN-4230): complete Step 1 — separate review label hit target

Fusion-Task-Id: FN-4230
Fusion-Task-Lineage: 99ad972d-a633-4905-b8e1-d606f4237b7d
This commit is contained in:
Fusion
2026-05-13 05:00:33 -07:00
committed by gsxdsm
parent 0dc6ac98ba
commit 0178a5a299
2 changed files with 87 additions and 11 deletions

View File

@@ -298,7 +298,7 @@ export function TaskReviewTab({ task, projectId, onTaskUpdated, addToast }: Prop
{!loading && !error && displayItems.length > 0 ? ( {!loading && !error && displayItems.length > 0 ? (
<ul className="task-review-tab__list"> <ul className="task-review-tab__list">
{displayItems.map((item) => { {displayItems.map((item) => {
const checkboxId = `task-review-item-${item.id}`; const checkboxId = `task-review-item-checkbox-${item.id}`;
return ( return (
<li key={item.id} className="task-review-tab__item card"> <li key={item.id} className="task-review-tab__item card">
@@ -309,11 +309,11 @@ export function TaskReviewTab({ task, projectId, onTaskUpdated, addToast }: Prop
<span className="task-review-tab__item-summary">{item.path ? `${item.path}: ` : ""}{item.summary}</span> <span className="task-review-tab__item-summary">{item.path ? `${item.path}: ` : ""}{item.summary}</span>
<span className={`task-review-tab__status task-review-tab__status--${item.status}`}>{item.status}</span> <span className={`task-review-tab__status task-review-tab__status--${item.status}`}>{item.status}</span>
</div> </div>
<div className="task-review-tab__meta">{formatTimestamp(item.createdAt)}</div>
{item.addressing ? (
<div className="task-review-tab__meta">Selected: {formatTimestamp(item.addressing.selectedAt)}{item.addressing.startedAt ? ` · Started: ${formatTimestamp(item.addressing.startedAt)}` : ""}{item.addressing.completedAt ? ` · Completed: ${formatTimestamp(item.addressing.completedAt)}` : ""}{item.addressing.error ? ` · Error: ${item.addressing.error}` : ""}</div>
) : null}
</label> </label>
<div className="task-review-tab__meta">{formatTimestamp(item.createdAt)}</div>
{item.addressing ? (
<div className="task-review-tab__meta">Selected: {formatTimestamp(item.addressing.selectedAt)}{item.addressing.startedAt ? ` · Started: ${formatTimestamp(item.addressing.startedAt)}` : ""}{item.addressing.completedAt ? ` · Completed: ${formatTimestamp(item.addressing.completedAt)}` : ""}{item.addressing.error ? ` · Error: ${item.addressing.error}` : ""}</div>
) : null}
{renderMarkdown ? ( {renderMarkdown ? (
<div className="task-review-tab__body markdown-body"> <div className="task-review-tab__body markdown-body">
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}> <ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>

View File

@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react"; import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { TaskReviewTab } from "../TaskReviewTab"; import { TaskReviewTab } from "../TaskReviewTab";
import { makeTask } from "./TaskDetailModal.test-helpers"; import { makeTask } from "./TaskDetailModal.test-helpers";
import { loadAllAppCss } from "../../test/cssFixture";
const REVIEW_MARKDOWN_TOGGLE_STORAGE_KEY = "fn-task-review-markdown"; const REVIEW_MARKDOWN_TOGGLE_STORAGE_KEY = "fn-task-review-markdown";
@@ -145,7 +146,41 @@ describe("TaskReviewTab", () => {
expect(screen.getByText("failed").className).toContain("task-review-tab__status--failed"); expect(screen.getByText("failed").className).toContain("task-review-tab__status--failed");
}); });
it("FN-4137 keeps markdown body clicks from toggling the review-item checkbox", async () => { it("keeps review body outside the checkbox label and preserves selection on body clicks", async () => {
const task = makeTask({
reviewState: {
source: "reviewer-agent",
summary: { verdict: "REVISE", reviewType: "code", summary: "Needs fixes" },
items: [
{
id: "reviewer-plain-click-1",
body: "plain review body",
author: { login: "reviewer-agent" },
createdAt: new Date().toISOString(),
summary: "Plain body click target",
},
],
addressing: [],
},
});
window.localStorage.setItem(REVIEW_MARKDOWN_TOGGLE_STORAGE_KEY, "false");
apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null });
const { container } = render(<TaskReviewTab task={task} addToast={vi.fn()} />);
const checkbox = await screen.findByRole("checkbox");
expect(checkbox).not.toBeChecked();
const body = container.querySelector(".task-review-tab__body");
expect(body).not.toBeNull();
expect(body?.closest("label")).toBeNull();
fireEvent.click(body as HTMLElement);
expect(checkbox).not.toBeChecked();
});
it("renders markdown mode body outside label and clicking links does not toggle selection", async () => {
const task = makeTask({ const task = makeTask({
reviewState: { reviewState: {
source: "reviewer-agent", source: "reviewer-agent",
@@ -153,7 +188,7 @@ describe("TaskReviewTab", () => {
items: [ items: [
{ {
id: "reviewer-markdown-click-1", id: "reviewer-markdown-click-1",
body: "**bold**\n\n[details](https://example.test/review)", body: "[example](https://example.com)",
author: { login: "reviewer-agent" }, author: { login: "reviewer-agent" },
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
summary: "Markdown body click target", summary: "Markdown body click target",
@@ -163,18 +198,49 @@ describe("TaskReviewTab", () => {
}, },
}); });
window.localStorage.setItem(REVIEW_MARKDOWN_TOGGLE_STORAGE_KEY, "true");
apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null }); apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null });
render(<TaskReviewTab task={task} addToast={vi.fn()} />); const { container } = render(<TaskReviewTab task={task} addToast={vi.fn()} />);
const checkbox = await screen.findByRole("checkbox"); const checkbox = await screen.findByRole("checkbox");
expect(checkbox).not.toBeChecked(); const link = await screen.findByRole("link", { name: "example" });
expect(container.querySelector(".task-review-tab__body")?.closest("label")).toBeNull();
fireEvent.click(screen.getByText("bold")); expect(link.closest("label")).toBeNull();
fireEvent.click(link);
expect(checkbox).not.toBeChecked(); expect(checkbox).not.toBeChecked();
}); });
it("renders plain mode body outside label when markdown rendering is disabled", async () => {
const task = makeTask({
reviewState: {
source: "reviewer-agent",
summary: { verdict: "REVISE", reviewType: "code", summary: "Needs fixes" },
items: [
{
id: "reviewer-plain-click-2",
body: "[example](https://example.com)",
author: { login: "reviewer-agent" },
createdAt: new Date().toISOString(),
summary: "Plain mode item",
},
],
addressing: [],
},
});
window.localStorage.setItem(REVIEW_MARKDOWN_TOGGLE_STORAGE_KEY, "false");
apiMocks.fetchTaskReview.mockResolvedValue({ reviewState: task.reviewState, automationStatus: null, emptyMessage: null });
const { container } = render(<TaskReviewTab task={task} addToast={vi.fn()} />);
await screen.findByText("Plain mode item");
const body = container.querySelector("pre.task-review-tab__body");
expect(body).not.toBeNull();
expect(body?.closest("label")).toBeNull();
});
it("renders markdown by default and persists plain-text toggle preference", async () => { it("renders markdown by default and persists plain-text toggle preference", async () => {
const task = makeTask({ const task = makeTask({
reviewState: { reviewState: {
@@ -417,6 +483,16 @@ describe("TaskReviewTab", () => {
expect(screen.getByText(/Error: Patch failed/)).toBeInTheDocument(); expect(screen.getByText(/Error: Patch failed/)).toBeInTheDocument();
}); });
it("keeps mobile actions wrapping contract and removes equal-width flex buttons", async () => {
const css = await loadAllAppCss();
const mobileMediaStart = css.indexOf("@media (max-width: 768px)");
expect(mobileMediaStart).toBeGreaterThanOrEqual(0);
const mobileCss = css.slice(mobileMediaStart);
expect(mobileCss).toMatch(/\.task-review-tab__actions\s*\{[^}]*flex-wrap\s*:\s*wrap\s*;[^}]*\}/);
expect(mobileCss).not.toMatch(/\.task-review-tab__actions\s+\.btn\s*\{[^}]*flex\s*:\s*1\s*;[^}]*\}/);
});
it("submits reviewer-agent selections through same revision action", async () => { it("submits reviewer-agent selections through same revision action", async () => {
const task = makeTask(); const task = makeTask();
apiMocks.fetchTaskReview.mockResolvedValue({ apiMocks.fetchTaskReview.mockResolvedValue({