From a559e6b51af9718ac88d26bee1a0680b4ec94c16 Mon Sep 17 00:00:00 2001 From: cavidelizade Date: Fri, 17 Jul 2026 01:30:05 +0400 Subject: [PATCH] perf(ui): look up cycle/module names via maps on the issue list cycleName/moduleName did a linear cycles.find / modules.find for every row, and they're called a few times per row, so a large list was O(rows x cycles) every render. Build id -> name maps once per render and look up in O(1). This is a focused slice of #345; the broader row/card memoization it describes is a larger change left for a follow-up. Refs #345 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/src/pages/IssueListPage.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/pages/IssueListPage.tsx b/apps/web/src/pages/IssueListPage.tsx index e9502f9..a79e12f 100644 --- a/apps/web/src/pages/IssueListPage.tsx +++ b/apps/web/src/pages/IssueListPage.tsx @@ -590,14 +590,20 @@ export function IssueListPage() { const dp = listDisplay.displayProperties; const hasCol = (id: SavedViewDisplayPropertyId) => dp.has(id); + // Build id -> name maps once per render instead of doing an O(cycles) / + // O(modules) find for every row. (Plain consts, not useMemo, because this + // sits after the component's early returns.) + const cycleNameById = new Map(cycles.map((c) => [c.id, c.name])); + const moduleNameById = new Map(modules.map((m) => [m.id, m.name])); + const cycleName = (issue: IssueApiResponse) => { const id = issue.cycle_ids?.[0]; - return id ? (cycles.find((c) => c.id === id)?.name ?? '—') : '—'; + return id ? (cycleNameById.get(id) ?? '—') : '—'; }; const moduleName = (issue: IssueApiResponse) => { const id = issue.module_ids?.[0]; - return id ? (modules.find((m) => m.id === id)?.name ?? '—') : '—'; + return id ? (moduleNameById.get(id) ?? '—') : '—'; }; const layout = parseIssueLayout(searchParams.get('layout'));