Main process — worker thread for team data: - New team-data-worker thread handles getTeamData and findLogsForTask, isolating heavy file I/O (scanning 300+ subagent JSONL files) from Electron's main event loop. getTeamData dropped from ~2000ms on the main thread to ~110ms via the worker. - Worker-side dedup and 10s result cache for findLogsForTask prevents redundant scans when the same task is queried multiple times. - Discovery cache TTL raised from 5s to 30s — avoids re-scanning the entire project directory on every call. - Message cap at 200 in TeamDataService to keep IPC payloads under 1MB (was sending 2200+ messages / ~3MB, stalling Chromium IPC serialization). - IPC handlers fall back to main-thread execution if the worker is unavailable (graceful degradation). Renderer — useShallow and memoization (55 files): - Added useShallow to store selectors across 55 renderer files. Batched individual useStore() calls (e.g. 17 calls in ExtensionStoreView, 10 in ConnectionSection) into single useShallow selectors, cutting unnecessary re-render checks on every store update. - MemberLogsTab: three 5-second polling intervals now pause when the parent tab is hidden (display:none). Previously 5 hidden tabs × 3 intervals = 15 polling timers firing continuously. - KanbanColumn wrapped in React.memo to skip re-renders when props haven't changed. - MemberList: memoized activeMembers/removedMembers/colorMap; replaced O(n×m) per-member task scan with a pre-computed reviewer map. - Bounded timer Maps in store initialization to prevent unbounded growth of debounce/throttle tracking maps during long sessions.
102 lines
3.3 KiB
TypeScript
102 lines
3.3 KiB
TypeScript
/**
|
|
* UpdateBanner - Slim top banner for download progress and restart prompt.
|
|
*
|
|
* Visible during download and after the update is ready to install.
|
|
*/
|
|
|
|
import { useStore } from '@renderer/store';
|
|
import { CheckCircle, Loader2, X } from 'lucide-react';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
export const UpdateBanner = (): React.JSX.Element | null => {
|
|
const {
|
|
showUpdateBanner,
|
|
updateStatus,
|
|
downloadProgress,
|
|
availableVersion,
|
|
installUpdate,
|
|
dismissUpdateBanner,
|
|
} = useStore(
|
|
useShallow((s) => ({
|
|
showUpdateBanner: s.showUpdateBanner,
|
|
updateStatus: s.updateStatus,
|
|
downloadProgress: s.downloadProgress,
|
|
availableVersion: s.availableVersion,
|
|
installUpdate: s.installUpdate,
|
|
dismissUpdateBanner: s.dismissUpdateBanner,
|
|
}))
|
|
);
|
|
|
|
if (!showUpdateBanner || (updateStatus !== 'downloading' && updateStatus !== 'downloaded')) {
|
|
return null;
|
|
}
|
|
|
|
const isDownloading = updateStatus === 'downloading';
|
|
const percent = Math.round(downloadProgress);
|
|
const clampedPercent = Math.max(0, Math.min(percent, 100));
|
|
|
|
return (
|
|
<div
|
|
className="relative border-b px-4 py-2.5"
|
|
style={{
|
|
backgroundColor: 'var(--color-surface)',
|
|
borderColor: 'var(--color-border)',
|
|
}}
|
|
>
|
|
{isDownloading ? (
|
|
<div className="pr-8">
|
|
<div
|
|
className="mb-1.5 flex items-center gap-2 text-xs"
|
|
style={{ color: 'var(--color-text-secondary)' }}
|
|
>
|
|
<Loader2 className="size-3.5 shrink-0 animate-spin text-blue-600 dark:text-blue-400" />
|
|
<span>Updating app</span>
|
|
<span className="tabular-nums" style={{ color: 'var(--color-text-muted)' }}>
|
|
{clampedPercent}%
|
|
</span>
|
|
</div>
|
|
<div
|
|
className="h-1 w-full overflow-hidden rounded-full"
|
|
style={{ backgroundColor: 'var(--color-border)' }}
|
|
>
|
|
<div
|
|
className="h-full rounded-full bg-blue-600 transition-all duration-300 ease-out dark:bg-blue-500"
|
|
style={{ width: `${clampedPercent}%` }}
|
|
/>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex items-center gap-2 pr-8">
|
|
<CheckCircle className="size-4 shrink-0 text-green-400" />
|
|
<span className="text-sm" style={{ color: 'var(--color-text-secondary)' }}>
|
|
Update ready
|
|
{availableVersion ? (
|
|
<span className="ml-1 text-xs" style={{ color: 'var(--color-text-muted)' }}>
|
|
v{availableVersion}
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
<button
|
|
onClick={installUpdate}
|
|
className="ml-auto rounded-md border px-2.5 py-1 text-xs font-medium transition-colors hover:bg-white/5"
|
|
style={{
|
|
borderColor: 'var(--color-border-emphasis)',
|
|
color: 'var(--color-text)',
|
|
}}
|
|
>
|
|
Restart now
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* Dismiss */}
|
|
<button
|
|
onClick={dismissUpdateBanner}
|
|
className="absolute right-3 top-1/2 shrink-0 -translate-y-1/2 rounded p-0.5 transition-colors hover:bg-white/10"
|
|
style={{ color: 'var(--color-text-muted)' }}
|
|
>
|
|
<X className="size-3.5" />
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|