From da7c82ebea37e930c051bfbd389d7c6b7cd7cb78 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 3 Jun 2026 13:24:21 -0700 Subject: [PATCH] FN-5947: fix mobile task review header spacing Tighten the Task Review tab layout on mobile and lock in the responsive header contract. - override the mobile .task-review-tab__summary-wrap flex rule so the summary block stops reserving excessive blank space - extend TaskReviewTab coverage to assert the mobile summary-wrap override and prevent regressions between empty and populated review states across reviewer-agent and pull-request sources - keep the review header structure stable across both review sources and empty/populated states Files changed: .../dashboard/app/components/TaskReviewTab.css | 4 + .../components/__tests__/TaskReviewTab.test.tsx | 98 ++++++++++++++++++++++ 2 files changed, 102 insertions(+) Fusion-Task-Id: FN-5947 Fusion-Task-Lineage: 813e1142-bf28-4687-8510-2f93ec40e582 --- .../app/components/TaskReviewTab.css | 4 + .../__tests__/TaskReviewTab.test.tsx | 98 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/packages/dashboard/app/components/TaskReviewTab.css b/packages/dashboard/app/components/TaskReviewTab.css index d2b3146605..5a90a11a4a 100644 --- a/packages/dashboard/app/components/TaskReviewTab.css +++ b/packages/dashboard/app/components/TaskReviewTab.css @@ -274,6 +274,10 @@ width: 100%; } + .task-review-tab__summary-wrap { + flex: 0 0 auto; + } + .task-review-tab__actions { justify-content: flex-start; gap: var(--space-sm); diff --git a/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx b/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx index 5e08435269..0e2b8b364f 100644 --- a/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx +++ b/packages/dashboard/app/components/__tests__/TaskReviewTab.test.tsx @@ -504,8 +504,13 @@ describe("TaskReviewTab", () => { const mobileMediaStart = css.indexOf("@media (max-width: 768px)"); expect(mobileMediaStart).toBeGreaterThanOrEqual(0); const mobileCss = css.slice(mobileMediaStart); + const baseSummaryWrapRule = css.match(/\.task-review-tab__summary-wrap\s*\{[^}]*\}/)?.[0] ?? ""; + expect(baseSummaryWrapRule).toMatch(/flex\s*:\s*1\s+1\s+20rem\s*;/); + expect(baseSummaryWrapRule).not.toMatch(/flex\s*:\s*0\s+0\s+auto\s*;/); expect(mobileCss).toMatch(/\.task-review-tab__header\s*\{[^}]*flex-direction\s*:\s*column\s*;[^}]*\}/); + expect(mobileCss).toMatch(/\.task-review-tab__summary-wrap\s*\{[^}]*flex\s*:\s*0\s+0\s+auto\s*;[^}]*\}/); + expect(mobileCss).not.toMatch(/\.task-review-tab__summary-wrap\s*\{[^}]*flex\s*:\s*1\s+1\s+20rem\s*;[^}]*\}/); expect(mobileCss).toMatch(/\.task-review-tab__actions\s*\{[^}]*justify-content\s*:\s*flex-start\s*;[^}]*\}/); expect(mobileCss).toMatch(/\.task-review-tab__actions\s+\.btn\s*\{[^}]*width\s*:\s*100%\s*;[^}]*\}/); expect(mobileCss).toMatch(/\.task-review-tab__body\s*\{[^}]*padding\s*:\s*var\(--space-sm\)\s*;[^}]*\}/); @@ -515,6 +520,99 @@ describe("TaskReviewTab", () => { expect(css).toMatch(/\.task-review-tab__item\s*\{[^}]*padding\s*:\s*var\(--card-padding\)\s*;[^}]*\}/); }); + it("preserves review header structure across sources and empty or populated states", async () => { + const cases = [ + { + task: makeTask({ id: "FN-100" }), + response: { + reviewState: { + source: "reviewer-agent" as const, + summary: { summary: "reviewer-agent", verdict: "REVISE", reviewType: "code" }, + items: [], + addressing: [], + }, + automationStatus: null, + emptyMessage: "No reviewer feedback yet — this task has not produced reviewer-agent feedback in direct mode.", + }, + summaryText: "reviewer-agent · 0 review item(s)", + emptyText: "No reviewer feedback yet — this task has not produced reviewer-agent feedback in direct mode.", + }, + { + task: makeTask({ id: "FN-101" }), + response: { + reviewState: { + source: "reviewer-agent" as const, + summary: { summary: "Needs fixes", verdict: "REVISE", reviewType: "code" }, + items: [{ id: "reviewer-item-1", body: "Fix failing test", author: { login: "reviewer-agent" }, createdAt: new Date().toISOString(), summary: "Fix failing test" }], + addressing: [], + }, + automationStatus: null, + emptyMessage: null, + }, + summaryText: "Needs fixes · 1 review item(s)", + itemText: "Fix failing test", + }, + { + task: makeTask({ id: "FN-102" }), + response: { + reviewState: { + source: "pull-request" as const, + summary: { reviewDecision: "REVIEW_REQUIRED", reviewers: [], blockingReasons: [], checks: [] }, + items: [], + addressing: [], + }, + automationStatus: null, + emptyMessage: null, + }, + summaryText: "REVIEW_REQUIRED · 0 review item(s)", + emptyText: "No review items yet.", + }, + { + task: makeTask({ id: "FN-103" }), + response: { + reviewState: { + source: "pull-request" as const, + summary: { reviewDecision: "APPROVED", reviewers: [], blockingReasons: [], checks: [] }, + items: [{ id: "pr-item-1", body: "Looks good", author: { login: "octocat" }, createdAt: new Date().toISOString(), summary: "Looks good" }], + addressing: [], + }, + automationStatus: null, + emptyMessage: null, + }, + summaryText: "APPROVED · 1 review item(s)", + itemText: "Looks good", + }, + ]; + + apiMocks.fetchTaskReview + .mockResolvedValueOnce(cases[0].response) + .mockResolvedValueOnce(cases[1].response) + .mockResolvedValueOnce(cases[2].response) + .mockResolvedValueOnce(cases[3].response); + + const { container, rerender } = render(); + + for (const [index, testCase] of cases.entries()) { + if (index > 0) { + rerender(); + } + + expect(await screen.findByText(testCase.summaryText)).toBeInTheDocument(); + expect(container.querySelector(".task-review-tab__header")).not.toBeNull(); + expect(container.querySelector(".task-review-tab__summary-wrap")).not.toBeNull(); + expect(container.querySelector(".task-review-tab__summary-group")).not.toBeNull(); + expect(container.querySelector(".task-review-tab__actions")).not.toBeNull(); + + if (testCase.emptyText) { + expect(screen.getByText(testCase.emptyText)).toBeInTheDocument(); + } + + if (testCase.itemText) { + expect(screen.getByText(testCase.itemText)).toBeInTheDocument(); + } + } + }); + it("shows create PR action when in-review without prInfo and auth is available", async () => { const onRequestCreatePr = vi.fn(); const task = makeTask({ column: "in-review", prInfo: undefined });