diff --git a/apps/desktop/src/components/ScreenSourcePicker.tsx b/apps/desktop/src/components/ScreenSourcePicker.tsx index 0b47381..559e94e 100644 --- a/apps/desktop/src/components/ScreenSourcePicker.tsx +++ b/apps/desktop/src/components/ScreenSourcePicker.tsx @@ -62,7 +62,13 @@ export function ScreenSourcePicker({ open, onClose, onStart }: Props) { return; } let cancelled = false; - const CONCURRENCY = 4; + // Concurrency 2 (down from 4): Windows GDI BitBlt / PrintWindow on + // multiple source windows contends for the desktop compositor and + // the whole Tauri window stutters while 4+ captures are in flight. + // 2 in parallel keeps the compositor breathing and the picker grid + // stays scrollable. Total load time goes up marginally since most + // individual captures are GDI-bound, not thread-bound. + const CONCURRENCY = 2; void (async () => { const list = await listScreenSources(); if (cancelled) return; @@ -310,7 +316,7 @@ function SourceSection({ key={src.id} source={src} selected={selectedId === src.id} - onClick={() => onSelect(src.id)} + onSelect={onSelect} /> ))} @@ -322,20 +328,25 @@ function SourceSection({ // Keeps re-render work proportional to the number of updates instead of // "whole grid on every update" — which was the main reason scrolling felt // frozen during the initial thumbnail fan-in. +// +// The parent passes `onSelect(id)` rather than an inline `onClick`-arrow +// so the callback reference stays stable across renders; otherwise +// React.memo would always see a fresh function prop and re-render every +// card on every parent update. const SourceCard = memo(function SourceCard({ source, selected, - onClick, + onSelect, }: { source: ScreenSource; selected: boolean; - onClick: () => void; + onSelect: (id: string) => void; }) { const thumb = thumbnailDataUrl(source); return (