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.
85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
/**
|
|
* PaneResizeHandle - Draggable divider between adjacent panes.
|
|
* Uses the same mouse-event pattern as Sidebar.tsx for resize.
|
|
*/
|
|
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
|
|
import { useStore } from '@renderer/store';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
interface PaneResizeHandleProps {
|
|
leftPaneId: string;
|
|
rightPaneId: string;
|
|
}
|
|
|
|
export const PaneResizeHandle = ({ leftPaneId }: PaneResizeHandleProps): React.JSX.Element => {
|
|
const [isResizing, setIsResizing] = useState(false);
|
|
const resizePanes = useStore((s) => s.resizePanes);
|
|
const paneLayout = useStore(useShallow((s) => s.paneLayout));
|
|
|
|
const handleMouseMove = useCallback(
|
|
(e: MouseEvent) => {
|
|
if (!isResizing) return;
|
|
|
|
// Calculate the new width fraction based on mouse position relative to container
|
|
const container = document.getElementById('pane-container');
|
|
if (!container) return;
|
|
|
|
const containerRect = container.getBoundingClientRect();
|
|
const relativeX = e.clientX - containerRect.left;
|
|
const newFraction = relativeX / containerRect.width;
|
|
|
|
// Calculate the cumulative width of all panes before the left pane
|
|
const leftPaneIndex = paneLayout.panes.findIndex((p) => p.id === leftPaneId);
|
|
if (leftPaneIndex === -1) return;
|
|
|
|
let cumulativeWidth = 0;
|
|
for (let i = 0; i < leftPaneIndex; i++) {
|
|
cumulativeWidth += paneLayout.panes[i].widthFraction;
|
|
}
|
|
|
|
const leftPaneNewWidth = newFraction - cumulativeWidth;
|
|
resizePanes(leftPaneId, leftPaneNewWidth);
|
|
},
|
|
[isResizing, leftPaneId, paneLayout.panes, resizePanes]
|
|
);
|
|
|
|
const handleMouseUp = useCallback(() => {
|
|
setIsResizing(false);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isResizing) {
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
document.body.style.cursor = 'col-resize';
|
|
document.body.style.userSelect = 'none';
|
|
}
|
|
|
|
return () => {
|
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
document.body.style.cursor = '';
|
|
document.body.style.userSelect = '';
|
|
};
|
|
}, [isResizing, handleMouseMove, handleMouseUp]);
|
|
|
|
const handleMouseDown = (e: React.MouseEvent): void => {
|
|
e.preventDefault();
|
|
setIsResizing(true);
|
|
};
|
|
|
|
return (
|
|
// eslint-disable-next-line jsx-a11y/no-static-element-interactions -- resize handle requires mouse interaction
|
|
<div
|
|
className={`flex w-1 shrink-0 cursor-col-resize items-center justify-center transition-colors hover:bg-blue-500/50 ${
|
|
isResizing ? 'bg-blue-500/50' : ''
|
|
}`}
|
|
style={{
|
|
backgroundColor: isResizing ? undefined : 'var(--color-border)',
|
|
}}
|
|
onMouseDown={handleMouseDown}
|
|
/>
|
|
);
|
|
};
|