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.
172 lines
5.4 KiB
TypeScript
172 lines
5.4 KiB
TypeScript
/**
|
|
* SettingsView - Main settings panel with all app configuration options.
|
|
* Provides UI for managing notifications, display settings, and advanced options.
|
|
*/
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { useStore } from '@renderer/store';
|
|
import { Loader2 } from 'lucide-react';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
|
|
import { useSettingsConfig, useSettingsHandlers } from './hooks';
|
|
import {
|
|
AdvancedSection,
|
|
ConnectionSection,
|
|
GeneralSection,
|
|
NotificationsSection,
|
|
} from './sections';
|
|
import { type SettingsSection, SettingsTabs } from './SettingsTabs';
|
|
|
|
export const SettingsView = (): React.JSX.Element | null => {
|
|
const [activeSection, setActiveSection] = useState<SettingsSection>('general');
|
|
const { pendingSettingsSection, clearPendingSettingsSection } = useStore(
|
|
useShallow((s) => ({
|
|
pendingSettingsSection: s.pendingSettingsSection,
|
|
clearPendingSettingsSection: s.clearPendingSettingsSection,
|
|
}))
|
|
);
|
|
|
|
// Consume pending section (avoid setState during render)
|
|
useEffect(() => {
|
|
if (pendingSettingsSection) {
|
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- intentional sync on prop change
|
|
setActiveSection(pendingSettingsSection as SettingsSection);
|
|
clearPendingSettingsSection();
|
|
}
|
|
}, [pendingSettingsSection, clearPendingSettingsSection]);
|
|
|
|
const {
|
|
config,
|
|
safeConfig,
|
|
loading,
|
|
saving,
|
|
error,
|
|
setError,
|
|
setSaving,
|
|
setConfig,
|
|
setOptimisticConfig,
|
|
updateConfig,
|
|
ignoredRepositoryItems,
|
|
excludedRepositoryIds,
|
|
isSnoozed,
|
|
} = useSettingsConfig();
|
|
|
|
const handlers = useSettingsHandlers({
|
|
config,
|
|
setSaving,
|
|
setError,
|
|
setConfig,
|
|
setOptimisticConfig,
|
|
updateConfig,
|
|
});
|
|
|
|
// Loading state
|
|
if (loading) {
|
|
return (
|
|
<div
|
|
className="flex flex-1 items-center justify-center"
|
|
style={{ backgroundColor: 'var(--color-surface)' }}
|
|
>
|
|
<div className="flex items-center gap-3" style={{ color: 'var(--color-text-muted)' }}>
|
|
<Loader2 className="size-5 animate-spin" />
|
|
<span>Loading settings...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Error state
|
|
if (error && !config) {
|
|
return (
|
|
<div
|
|
className="flex flex-1 items-center justify-center"
|
|
style={{ backgroundColor: 'var(--color-surface)' }}
|
|
>
|
|
<div className="text-center">
|
|
<p className="mb-4 text-red-400">{error}</p>
|
|
<button
|
|
onClick={() => window.location.reload()}
|
|
className="rounded-md px-4 py-2 transition-colors"
|
|
style={{
|
|
backgroundColor: 'var(--color-surface-raised)',
|
|
color: 'var(--color-text-secondary)',
|
|
}}
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!config) return null;
|
|
|
|
return (
|
|
<div className="flex-1 overflow-auto" style={{ backgroundColor: 'var(--color-surface)' }}>
|
|
<div className="mx-auto max-w-2xl px-6 py-8">
|
|
{/* Header */}
|
|
<div className="mb-6">
|
|
<h1 className="text-lg font-medium" style={{ color: 'var(--color-text)' }}>
|
|
Settings
|
|
</h1>
|
|
<p className="text-sm" style={{ color: 'var(--color-text-muted)' }}>
|
|
Manage your app preferences
|
|
</p>
|
|
{error && (
|
|
<div className="mt-4 rounded-md border border-red-500/20 bg-red-500/10 px-3 py-2">
|
|
<p className="text-sm text-red-400">{error}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Tabs */}
|
|
<SettingsTabs activeSection={activeSection} onSectionChange={setActiveSection} />
|
|
|
|
{/* Content */}
|
|
<div className="mt-4">
|
|
{activeSection === 'general' && (
|
|
<GeneralSection
|
|
safeConfig={safeConfig}
|
|
saving={saving}
|
|
onGeneralToggle={handlers.handleGeneralToggle}
|
|
onThemeChange={handlers.handleThemeChange}
|
|
onLanguageChange={handlers.handleLanguageChange}
|
|
/>
|
|
)}
|
|
|
|
{activeSection === 'connection' && <ConnectionSection />}
|
|
|
|
{activeSection === 'notifications' && (
|
|
<NotificationsSection
|
|
safeConfig={safeConfig}
|
|
saving={saving}
|
|
isSnoozed={isSnoozed}
|
|
ignoredRepositoryItems={ignoredRepositoryItems}
|
|
excludedRepositoryIds={excludedRepositoryIds}
|
|
onNotificationToggle={handlers.handleNotificationToggle}
|
|
onSnooze={handlers.handleSnooze}
|
|
onClearSnooze={handlers.handleClearSnooze}
|
|
onAddIgnoredRepository={handlers.handleAddIgnoredRepository}
|
|
onRemoveIgnoredRepository={handlers.handleRemoveIgnoredRepository}
|
|
onAddTrigger={handlers.handleAddTrigger}
|
|
onUpdateTrigger={handlers.handleUpdateTrigger}
|
|
onRemoveTrigger={handlers.handleRemoveTrigger}
|
|
onStatusChangeStatusesUpdate={handlers.handleStatusChangeStatusesUpdate}
|
|
/>
|
|
)}
|
|
|
|
{activeSection === 'advanced' && (
|
|
<AdvancedSection
|
|
saving={saving}
|
|
onResetToDefaults={handlers.handleResetToDefaults}
|
|
onExportConfig={handlers.handleExportConfig}
|
|
onImportConfig={handlers.handleImportConfig}
|
|
onOpenInEditor={handlers.handleOpenInEditor}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|