perf: isolate task surface rendering (#3226)

This commit is contained in:
Carlos Florêncio
2026-09-06 13:30:24 +01:00
committed by GitHub
parent fed90594d3
commit bab9e641fa
28 changed files with 1475 additions and 184 deletions
@@ -4,7 +4,7 @@ import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-libra
import { TooltipProvider } from "@kandev/ui/tooltip";
import { StateProvider } from "@/components/state-provider";
import { TaskContributionIcons } from "@/components/task/task-contribution-icons";
import { PRTaskIcon } from "./pr-task-icon";
import { normalizeTaskPRs, PRTaskIcon } from "./pr-task-icon";
import type { AppState } from "@/lib/state/store";
import type { TaskPR } from "@/lib/types/github";
import type { TaskCIAutomationOptions } from "@/lib/types/github";
@@ -100,6 +100,11 @@ beforeEach(() => {
afterEach(() => cleanup());
describe("PRTaskIcon corrupted store entry", () => {
it("reuses one empty list for missing or malformed PR data", () => {
expect(normalizeTaskPRs(undefined)).toBe(normalizeTaskPRs(null));
expect(normalizeTaskPRs({})).toBe(normalizeTaskPRs([]));
});
// Regression: an upstream payload (partial hydration, WS reorder, etc.) once
// landed in taskPRs.byTaskId[TASK_ID] as a non-array truthy value. The
// length-based guards then fell through into MultiPRIcon, where for-of
+6 -1
View File
@@ -39,6 +39,7 @@ const SKY_400 = CHANGE_REQUEST_STATUS_COLORS.review;
const EMERALD_400 = CHANGE_REQUEST_STATUS_COLORS.ready;
const QUEUED = CHANGE_REQUEST_STATUS_COLORS.queued;
const GREEN_500 = CHANGE_REQUEST_STATUS_COLORS.passing;
const EMPTY_PRS: TaskPR[] = [];
/** Maps the task-level PR projection to the same visual language as live PRs. */
export function getPRAggregateStatusColor(state: string | null | undefined): string {
@@ -253,7 +254,7 @@ export function pickDefaultPR(prs: TaskPR[]): TaskPR | null {
export function PRTaskIcon({ taskId, prInfo }: { taskId: string; prInfo?: TaskPRInfo }) {
const prs = useAppStore((state) => getTaskPRsForCurrentWorkspace(state, taskId));
const hydration = useTaskPRTooltipHydration(taskId, { includeAutomation: true });
const fullPRs = Array.isArray(prs) && prs.length > 0 ? prs : [];
const fullPRs = normalizeTaskPRs(prs);
// Defensive: an upstream payload may briefly seed byTaskId[taskId] with a
// non-array value (e.g. an empty object from a partial hydration). Bail
@@ -271,6 +272,10 @@ export function PRTaskIcon({ taskId, prInfo }: { taskId: string; prInfo?: TaskPR
);
}
export function normalizeTaskPRs(prs: unknown): TaskPR[] {
return Array.isArray(prs) && prs.length > 0 ? prs : EMPTY_PRS;
}
type TaskPRIconPresentation = {
hasFullData: boolean;
singlePR: TaskPR | null;