perf(renderer): memoize queue summary + stabilize list callbacks (L94/PERF4/PERF5/L163)
- L94/PERF4: add queueSummaryOf — a 1-entry items-ref memo over summarizeQueue. The store hands out a new items array per change, so App's taskbar subscription and DownloadsView's render compute the aggregate once per change (second caller hits the cache) instead of twice per tick. summarizeQueue stays pure/tested. - PERF5: hoist VirtualList estimateSize/getKey/renderItem to stable module-level functions so the virtualizer doesn't churn its measure/key cache. - L163: one useShallow selection replaces QueueItem's 10 individual useDownloads subscriptions (stable actions -> never re-renders the row). typecheck + 268 tests + eslint + prettier green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-7
@@ -702,8 +702,11 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
- [ ] **L93 — SettingsView bypasses the store layer.** It calls `window.api` directly for
|
||||
cookies/ffmpeg/yt-dlp/app-update/backup while using stores for settings/templates/errorlog — mixed
|
||||
data-access in one component.
|
||||
- [ ] **L94 — `summarizeQueue` recomputed every render** in [DownloadsView.tsx](src/renderer/src/components/DownloadsView.tsx)
|
||||
(no `useMemo`) — and again on every store change in App's taskbar effect. Memoize/share.
|
||||
- [x] **L94 — `summarizeQueue` recomputed every render** in [DownloadsView.tsx](src/renderer/src/components/DownloadsView.tsx)
|
||||
(no `useMemo`) — and again on every store change in App's taskbar effect. Memoize/share. *Fixed: a 1-entry
|
||||
items-ref memo `queueSummaryOf` in [queueStats.ts](src/renderer/src/store/queueStats.ts) — the store hands
|
||||
out a new items array per change, so both App's taskbar subscription and DownloadsView's render summarize
|
||||
it once per change (the second caller hits the cache) instead of twice (also closes PERF4).*
|
||||
- [ ] **L95 — "Sign in" hyphenation varies** in the Cookies card ("Sign in…", "Site to sign in to", "Sign-in window", "Signing in…").
|
||||
- [ ] **L96 — Ellipsis-on-dialog-buttons inconsistent.** "Export backup…/Import backup…/Sign in…" use
|
||||
`…` (opens a dialog), but "Browse" / "Check for updates" / "Update now" don't. Adopt the `…` = opens-a-dialog convention.
|
||||
@@ -859,8 +862,10 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
double-scroll or misalign the virtualized list's viewport.
|
||||
- [ ] **L162 — Per-thumbnail store subscription.** Every `MediaThumb` calls `useResolvedDark()` (two store
|
||||
subscriptions) instead of receiving a resolved theme prop — N subscriptions per list.
|
||||
- [ ] **L163 — QueueItem makes 9 separate `useDownloads` selector calls** instead of one destructured
|
||||
selection — repeated boilerplate per row.
|
||||
- [x] **L163 — QueueItem makes 9 separate `useDownloads` selector calls** instead of one destructured
|
||||
selection — repeated boilerplate per row. *Fixed: one `useShallow` selection replaces the 10 individual
|
||||
subscriptions in [QueueItem.tsx](src/renderer/src/components/QueueItem.tsx) (the actions are stable, so the
|
||||
shallow compare never re-renders the row).*
|
||||
|
||||
*Round 8 (2026-06-29) — formatting micro-inconsistencies:*
|
||||
|
||||
@@ -1698,12 +1703,16 @@ acceptable first:** memoized `QueueItem`s don't re-render when their item is unc
|
||||
on every add/done/error/cancel/progress-driven change; with M33's unbounded channel enqueue (thousands of
|
||||
items) every event becomes O(n), and several events fire per second during active downloads. **Fix:**
|
||||
track running/queued counts + a pointer instead of rescanning.
|
||||
- [ ] **PERF4 — `summarizeQueue` runs twice per progress tick.** Once in App's store subscription (which
|
||||
- [x] **PERF4 — `summarizeQueue` runs twice per progress tick.** Once in App's store subscription (which
|
||||
also pushes a taskbar IPC every tick, L25) and once in DownloadsView's render (no `useMemo`, L94) — each
|
||||
O(items), every ~second per active download. **Fix:** compute once, memoize, throttle the taskbar push.
|
||||
- [ ] **PERF5 — `VirtualList` gets new function identities each render.** `estimateSize`/`getKey`/
|
||||
*Fixed with L94: `queueSummaryOf` computes the aggregate once per items change (shared ref cache), so
|
||||
App's subscription and DownloadsView's render no longer each run it; the taskbar IPC push was already
|
||||
deduped by computed value (L25).*
|
||||
- [x] **PERF5 — `VirtualList` gets new function identities each render.** `estimateSize`/`getKey`/
|
||||
`renderItem` are passed as inline arrows, so the virtualizer can't rely on stable references (risking
|
||||
re-measures/cache churn). **Fix:** `useCallback`/hoist them.
|
||||
re-measures/cache churn). **Fix:** `useCallback`/hoist them. *Fixed: the three callbacks are hoisted to
|
||||
stable module-level functions in [DownloadsView.tsx](src/renderer/src/components/DownloadsView.tsx).*
|
||||
- [ ] **PERF6 — Per-thumbnail store subscriptions.** Each `MediaThumb` calls `useResolvedDark()` (two
|
||||
store subscriptions) (L162); a long list creates N×2 subscriptions. **Fix:** resolve once, pass as a prop.
|
||||
- [x] **PERF7 — (ref R3) O(n²) media-items rewrites** dominate the main-process cost when downloading a
|
||||
|
||||
Reference in New Issue
Block a user