perf(renderer): stable callbacks and lazy-load large dialogs

Move toggleSidebarSessionSelection into SessionItem's own store
subscription, eliminating the inline arrow function prop that was
breaking its memo on every sidebar render.

Lazy-load LaunchTeamDialog (2918L) and CreateTeamDialog (2208L) in all
four host components (TeamDetailView, TeamListView, SchedulesView,
ScheduleSection). These dialogs are never needed at initial mount — they
only open on user action. Deferring their parse/compile saves ~175KB of
JS from the initial render path.
This commit is contained in:
Mike 2026-05-02 21:10:24 +05:00
parent fa38b90f9c
commit 2bda324e1a
6 changed files with 105 additions and 83 deletions

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { lazy, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@renderer/components/ui/button'; import { Button } from '@renderer/components/ui/button';
import { Input } from '@renderer/components/ui/input'; import { Input } from '@renderer/components/ui/input';
@ -24,8 +24,11 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { LaunchTeamDialog } from '../team/dialogs/LaunchTeamDialog';
import { ScheduleRunLogDialog } from '../team/schedule/ScheduleRunLogDialog'; import { ScheduleRunLogDialog } from '../team/schedule/ScheduleRunLogDialog';
const LaunchTeamDialog = lazy(() =>
import('../team/dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
import { ScheduleRunRow } from '../team/schedule/ScheduleRunRow'; import { ScheduleRunRow } from '../team/schedule/ScheduleRunRow';
import { ScheduleStatusBadge } from '../team/schedule/ScheduleStatusBadge'; import { ScheduleStatusBadge } from '../team/schedule/ScheduleStatusBadge';
@ -562,6 +565,7 @@ export const SchedulesView = (): React.JSX.Element => {
</div> </div>
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<Suspense fallback={null}>
<LaunchTeamDialog <LaunchTeamDialog
mode="schedule" mode="schedule"
open={dialogOpen} open={dialogOpen}
@ -569,6 +573,7 @@ export const SchedulesView = (): React.JSX.Element => {
schedule={editingSchedule} schedule={editingSchedule}
onClose={handleClose} onClose={handleClose}
/> />
</Suspense>
</div> </div>
); );
}; };

View file

@ -202,7 +202,6 @@ export const DateGroupedSessions = memo((): React.JSX.Element => {
toggleShowHiddenSessions, toggleShowHiddenSessions,
sidebarSelectedSessionIds, sidebarSelectedSessionIds,
sidebarMultiSelectActive, sidebarMultiSelectActive,
toggleSidebarSessionSelection,
clearSidebarSelection, clearSidebarSelection,
toggleSidebarMultiSelect, toggleSidebarMultiSelect,
hideMultipleSessions, hideMultipleSessions,
@ -239,7 +238,6 @@ export const DateGroupedSessions = memo((): React.JSX.Element => {
toggleShowHiddenSessions: s.toggleShowHiddenSessions, toggleShowHiddenSessions: s.toggleShowHiddenSessions,
sidebarSelectedSessionIds: s.sidebarSelectedSessionIds, sidebarSelectedSessionIds: s.sidebarSelectedSessionIds,
sidebarMultiSelectActive: s.sidebarMultiSelectActive, sidebarMultiSelectActive: s.sidebarMultiSelectActive,
toggleSidebarSessionSelection: s.toggleSidebarSessionSelection,
clearSidebarSelection: s.clearSidebarSelection, clearSidebarSelection: s.clearSidebarSelection,
toggleSidebarMultiSelect: s.toggleSidebarMultiSelect, toggleSidebarMultiSelect: s.toggleSidebarMultiSelect,
hideMultipleSessions: s.hideMultipleSessions, hideMultipleSessions: s.hideMultipleSessions,
@ -1104,7 +1102,6 @@ export const DateGroupedSessions = memo((): React.JSX.Element => {
isHidden={item.isHidden} isHidden={item.isHidden}
multiSelectActive={sidebarMultiSelectActive} multiSelectActive={sidebarMultiSelectActive}
isSelected={selectedSet.has(item.session.id)} isSelected={selectedSet.has(item.session.id)}
onToggleSelect={() => toggleSidebarSessionSelection(item.session.id)}
/> />
)} )}
</div> </div>

View file

@ -30,7 +30,6 @@ interface SessionItemProps {
isHidden?: boolean; isHidden?: boolean;
multiSelectActive?: boolean; multiSelectActive?: boolean;
isSelected?: boolean; isSelected?: boolean;
onToggleSelect?: () => void;
} }
/** /**
@ -164,7 +163,6 @@ export const SessionItem = memo(
isHidden, isHidden,
multiSelectActive, multiSelectActive,
isSelected, isSelected,
onToggleSelect,
}: Readonly<SessionItemProps>): React.JSX.Element => { }: Readonly<SessionItemProps>): React.JSX.Element => {
const { const {
openTab, openTab,
@ -174,6 +172,7 @@ export const SessionItem = memo(
splitPane, splitPane,
togglePinSession, togglePinSession,
toggleHideSession, toggleHideSession,
toggleSidebarSessionSelection,
} = useStore( } = useStore(
useShallow((s) => ({ useShallow((s) => ({
openTab: s.openTab, openTab: s.openTab,
@ -183,6 +182,7 @@ export const SessionItem = memo(
splitPane: s.splitPane, splitPane: s.splitPane,
togglePinSession: s.togglePinSession, togglePinSession: s.togglePinSession,
toggleHideSession: s.toggleHideSession, toggleHideSession: s.toggleHideSession,
toggleSidebarSessionSelection: s.toggleSidebarSessionSelection,
})) }))
); );
@ -192,8 +192,8 @@ export const SessionItem = memo(
if (!activeProjectId) return; if (!activeProjectId) return;
// In multi-select mode, clicks toggle selection // In multi-select mode, clicks toggle selection
if (multiSelectActive && onToggleSelect) { if (multiSelectActive) {
onToggleSelect(); toggleSidebarSessionSelection(session.id);
return; return;
} }
@ -291,7 +291,7 @@ export const SessionItem = memo(
<input <input
type="checkbox" type="checkbox"
checked={isSelected ?? false} checked={isSelected ?? false}
onChange={() => onToggleSelect?.()} onChange={() => toggleSidebarSessionSelection(session.id)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
className="size-3.5 shrink-0 accent-blue-500" className="size-3.5 shrink-0 accent-blue-500"
/> />

View file

@ -80,7 +80,7 @@ import { useShallow } from 'zustand/react/shallow';
import { AddMemberDialog } from './dialogs/AddMemberDialog'; import { AddMemberDialog } from './dialogs/AddMemberDialog';
import { CreateTaskDialog } from './dialogs/CreateTaskDialog'; import { CreateTaskDialog } from './dialogs/CreateTaskDialog';
import { EditTeamDialog } from './dialogs/EditTeamDialog'; import { EditTeamDialog } from './dialogs/EditTeamDialog';
import { LaunchTeamDialog, type TeamLaunchDialogMode } from './dialogs/LaunchTeamDialog'; import type { TeamLaunchDialogMode } from './dialogs/LaunchTeamDialog';
import { ReviewDialog } from './dialogs/ReviewDialog'; import { ReviewDialog } from './dialogs/ReviewDialog';
import { SendMessageDialog } from './dialogs/SendMessageDialog'; import { SendMessageDialog } from './dialogs/SendMessageDialog';
import { TaskDetailDialog } from './dialogs/TaskDetailDialog'; import { TaskDetailDialog } from './dialogs/TaskDetailDialog';
@ -96,6 +96,9 @@ import type { AddMemberEntry } from './dialogs/AddMemberDialog';
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode'; import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
import type { ComponentProps, CSSProperties } from 'react'; import type { ComponentProps, CSSProperties } from 'react';
const LaunchTeamDialog = lazy(() =>
import('./dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
const ProjectEditorOverlay = lazy(() => const ProjectEditorOverlay = lazy(() =>
import('./editor/ProjectEditorOverlay').then((m) => ({ default: m.ProjectEditorOverlay })) import('./editor/ProjectEditorOverlay').then((m) => ({ default: m.ProjectEditorOverlay }))
); );
@ -2176,6 +2179,7 @@ export const TeamDetailView = memo(
</div> </div>
</div> </div>
</div> </div>
<Suspense fallback={null}>
<LaunchTeamDialog <LaunchTeamDialog
mode={launchDialogState.mode} mode={launchDialogState.mode}
open={launchDialogOpen} open={launchDialogOpen}
@ -2188,6 +2192,7 @@ export const TeamDetailView = memo(
onLaunch={handleLaunchDialogSubmit} onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit} onRelaunch={handleRelaunchDialogSubmit}
/> />
</Suspense>
</> </>
); );
} }
@ -2976,6 +2981,7 @@ export const TeamDetailView = memo(
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Suspense fallback={null}>
<LaunchTeamDialog <LaunchTeamDialog
mode={launchDialogState.mode} mode={launchDialogState.mode}
open={launchDialogOpen} open={launchDialogOpen}
@ -2989,6 +2995,7 @@ export const TeamDetailView = memo(
onLaunch={handleLaunchDialogSubmit} onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit} onRelaunch={handleRelaunchDialogSubmit}
/> />
</Suspense>
<SendMessageDialog <SendMessageDialog
open={sendDialogOpen} open={sendDialogOpen}

View file

@ -1,4 +1,4 @@
import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
import { recordRecentProjectOpenPaths } from '@features/recent-projects/renderer'; import { recordRecentProjectOpenPaths } from '@features/recent-projects/renderer';
import { api, isElectronMode } from '@renderer/api'; import { api, isElectronMode } from '@renderer/api';
@ -45,8 +45,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { CreateTeamDialog } from './dialogs/CreateTeamDialog'; import type { ActiveTeamRef, TeamCopyData } from './dialogs/CreateTeamDialog';
import { LaunchTeamDialog } from './dialogs/LaunchTeamDialog';
import { TeamEmptyState } from './TeamEmptyState'; import { TeamEmptyState } from './TeamEmptyState';
import { EMPTY_TEAM_FILTER, TeamListFilterPopover } from './TeamListFilterPopover'; import { EMPTY_TEAM_FILTER, TeamListFilterPopover } from './TeamListFilterPopover';
import { import {
@ -55,7 +54,13 @@ import {
teamMatchesProjectSelection, teamMatchesProjectSelection,
} from './teamProjectSelection'; } from './teamProjectSelection';
import type { ActiveTeamRef, TeamCopyData } from './dialogs/CreateTeamDialog'; const CreateTeamDialog = lazy(() =>
import('./dialogs/CreateTeamDialog').then((m) => ({ default: m.CreateTeamDialog }))
);
const LaunchTeamDialog = lazy(() =>
import('./dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
import type { TeamListFilterState } from './TeamListFilterPopover'; import type { TeamListFilterState } from './TeamListFilterPopover';
import type { TeamStatus } from '@renderer/utils/teamListStatus'; import type { TeamStatus } from '@renderer/utils/teamListStatus';
import type { import type {
@ -732,6 +737,7 @@ export const TeamListView = memo((): React.JSX.Element => {
} }
const createDialogElement = ( const createDialogElement = (
<Suspense fallback={null}>
<CreateTeamDialog <CreateTeamDialog
open={showCreateDialog} open={showCreateDialog}
canCreate={canCreate} canCreate={canCreate}
@ -746,9 +752,11 @@ export const TeamListView = memo((): React.JSX.Element => {
onCreate={handleCreateSubmit} onCreate={handleCreateSubmit}
onOpenTeam={openTeamTab} onOpenTeam={openTeamTab}
/> />
</Suspense>
); );
const launchDialogElement = ( const launchDialogElement = (
<Suspense fallback={null}>
<LaunchTeamDialog <LaunchTeamDialog
mode="launch" mode="launch"
open={launchDialogOpen} open={launchDialogOpen}
@ -761,6 +769,7 @@ export const TeamListView = memo((): React.JSX.Element => {
onClose={() => setLaunchDialogOpen(false)} onClose={() => setLaunchDialogOpen(false)}
onLaunch={handleLaunchSubmit} onLaunch={handleLaunchSubmit}
/> />
</Suspense>
); );
const renderHeader = (): React.JSX.Element => ( const renderHeader = (): React.JSX.Element => (

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { lazy, Suspense, useCallback, useEffect, useState } from 'react';
import { Button } from '@renderer/components/ui/button'; import { Button } from '@renderer/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'; import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
@ -18,9 +18,11 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { LaunchTeamDialog } from '../dialogs/LaunchTeamDialog';
import { ScheduleEmptyState } from './ScheduleEmptyState'; import { ScheduleEmptyState } from './ScheduleEmptyState';
const LaunchTeamDialog = lazy(() =>
import('../dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
import { ScheduleRunLogDialog } from './ScheduleRunLogDialog'; import { ScheduleRunLogDialog } from './ScheduleRunLogDialog';
import { ScheduleRunRow } from './ScheduleRunRow'; import { ScheduleRunRow } from './ScheduleRunRow';
import { ScheduleStatusBadge } from './ScheduleStatusBadge'; import { ScheduleStatusBadge } from './ScheduleStatusBadge';
@ -305,6 +307,7 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
)} )}
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<Suspense fallback={null}>
<LaunchTeamDialog <LaunchTeamDialog
mode="schedule" mode="schedule"
open={dialogOpen} open={dialogOpen}
@ -312,6 +315,7 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
schedule={editingSchedule} schedule={editingSchedule}
onClose={handleClose} onClose={handleClose}
/> />
</Suspense>
</div> </div>
); );
}; };