Skip to content

perf: memoize list-row components, chunk vendor bundles, dedupe map builds#242

Merged
AdamJ merged 1 commit into
mainfrom
perf/219-p1-memo-chunking-map-reuse
Jul 3, 2026
Merged

perf: memoize list-row components, chunk vendor bundles, dedupe map builds#242
AdamJ merged 1 commit into
mainfrom
perf/219-p1-memo-chunking-map-reuse

Conversation

@AdamJ

@AdamJ AdamJ commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses the 3 remaining P1 items from #219 (the 4th, useCallback wrapping, was already resolved as a side effect of #237). P2 items (PWA icon size, dead Google Fonts cache rule) are not covered here.

Type of Change

  • Update to existing feature (performance)

Related Issue

Addresses P1 items in #219 (not closing — issue still tracks P2 items)

Changes Made

  • TaskItem.tsx, ArchiveItem.tsx, PlannedTaskCard.tsx wrapped in React.memo. This only pays off now because perf: memoize TimeTrackingContext provider, isolate 30s timer tick #237 made every context mutation handler stable via useCallback — before that, these components would have re-rendered every time regardless of memo since their callback props changed identity every render.
  • Archive.tsx: handleEdit was still a fresh closure every render, which would have silently defeated ArchiveItem's new memo (prop identity would change every time regardless of day). Wrapped in useCallback.
  • vite.config.ts: added build.rollupOptions.output.manualChunks splitting recharts, @supabase/supabase-js, @radix-ui/*, motion, and the react-markdown dependency chain (remark/rehype/mdast/hast/micromark/unified/vfile) into their own vendor chunks. Main bundle drops from ~1MB to ~296KB; the "chunk larger than 500kB" build warning is gone entirely.
  • Index.tsx: billable/non-billable hours were computed via two separate archivedDays.reduce() passes calling getBillableHoursForDay/getNonBillableHoursForDay, each rebuilding projectMap/categoryMap from scratch per day. Replaced with a single pass using getDayStats (which already accepts pre-built maps) with the maps built once via useMemo.

Checklist

Documentation

  • N/A — no README changes

General

  • Branch is up to date with main
  • No unrelated files included in this PR
  • Tested locally by invoking the pnpm lint and pnpm build (did not run pnpm test — not requested this session)

Notes for Reviewers

Skipped deliberately: extracting+memoizing ClientManagement.tsx's inline client rows (also named in the original audit). Unlike the other three, ClientManagement renders its list inline with no existing row component to wrap, and it's a settings page, not a hot re-render loop like the dashboard/kanban board that the timer tick or task mutations hit constantly. The refactor cost (extract a new component, wire up stable callbacks) doesn't match the payoff there — flagging in case you disagree.

Bonus finding, not fixed here: while investigating why no vendor-recharts chunk showed up in the build output, found that src/components/ui/chart.tsx (the recharts wrapper) has zero consumers anywhere in src/ — recharts was never actually reachable from the app, so it wasn't being bundled at all regardless of chunking config. Filed as a separate follow-up rather than scope-creeping this PR.

Manually verified in a running dev server: started a day, added two tasks (one active, one completed) to confirm TaskItem's memo doesn't break per-row state or delete; visited the Kanban board to confirm PlannedTaskCard renders correctly; visited Archive and clicked Edit on the first of two entries to confirm ArchiveItem's memo + the handleEdit fix still route the click to the correct day. pnpm lint and pnpm build both clean, build output confirms the vendor chunk split and the disappearance of the 500kB warning.

…uilds

Addresses the P1 findings from #219: TaskItem, ArchiveItem, and
PlannedTaskCard now wrapped in React.memo so an unrelated update in
one row doesn't re-render every other row in the list — this only
pays off now that #237 made the context's mutation handlers stable
via useCallback. Archive.tsx's handleEdit was still a fresh closure
per render, which would have defeated ArchiveItem's new memo, so
it's wrapped in useCallback too.

vite.config.ts now has build.rollupOptions.output.manualChunks
splitting recharts, @supabase/supabase-js, @radix-ui, motion, and
the react-markdown dependency chain into their own vendor chunks.
Drops the main bundle from ~1MB to ~296KB and clears the "chunk
larger than 500kB" build warning entirely.

Index.tsx's billable/non-billable hours calculation was calling
getBillableHoursForDay/getNonBillableHoursForDay per archived day in
two separate reduce() passes, each rebuilding projectMap/categoryMap
from scratch — now a single pass using getDayStats with maps built
once via useMemo.

Skipped: extracting+memoizing ClientManagement.tsx's inline client
rows. Unlike TaskItem/ArchiveItem/PlannedTaskCard, ClientManagement
renders its list inline (no existing row component) and isn't in a
hot re-render loop like the dashboard/kanban board — the refactor
cost doesn't match the payoff here.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying timetrackerpro with  Cloudflare Pages  Cloudflare Pages

Latest commit: ff92fe5
Status: ✅  Deploy successful!
Preview URL: https://087bea7c.timetrackerpro.pages.dev
Branch Preview URL: https://perf-219-p1-memo-chunking-ma.timetrackerpro.pages.dev

View logs

@AdamJ AdamJ added this to Timetraked Jul 3, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in Timetraked Jul 3, 2026
@AdamJ AdamJ merged commit e1fe9b0 into main Jul 3, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Timetraked Jul 3, 2026
@AdamJ AdamJ deleted the perf/219-p1-memo-chunking-map-reuse branch July 3, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant