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,13 +565,15 @@ export const SchedulesView = (): React.JSX.Element => {
</div> </div>
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<LaunchTeamDialog <Suspense fallback={null}>
mode="schedule" <LaunchTeamDialog
open={dialogOpen} mode="schedule"
teamName={editingSchedule?.teamName} open={dialogOpen}
schedule={editingSchedule} teamName={editingSchedule?.teamName}
onClose={handleClose} schedule={editingSchedule}
/> 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,18 +2179,20 @@ export const TeamDetailView = memo(
</div> </div>
</div> </div>
</div> </div>
<LaunchTeamDialog <Suspense fallback={null}>
mode={launchDialogState.mode} <LaunchTeamDialog
open={launchDialogOpen} mode={launchDialogState.mode}
teamName={teamName} open={launchDialogOpen}
members={[]} teamName={teamName}
defaultProjectPath={draftTeamSummary?.projectPath} members={[]}
provisioningError={provisioningError} defaultProjectPath={draftTeamSummary?.projectPath}
clearProvisioningError={clearProvisioningError} provisioningError={provisioningError}
onClose={closeLaunchDialog} clearProvisioningError={clearProvisioningError}
onLaunch={handleLaunchDialogSubmit} onClose={closeLaunchDialog}
onRelaunch={handleRelaunchDialogSubmit} onLaunch={handleLaunchDialogSubmit}
/> onRelaunch={handleRelaunchDialogSubmit}
/>
</Suspense>
</> </>
); );
} }
@ -2976,19 +2981,21 @@ export const TeamDetailView = memo(
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<LaunchTeamDialog <Suspense fallback={null}>
mode={launchDialogState.mode} <LaunchTeamDialog
open={launchDialogOpen} mode={launchDialogState.mode}
teamName={teamName} open={launchDialogOpen}
members={membersWithLiveBranches} teamName={teamName}
defaultProjectPath={data.config.projectPath} members={membersWithLiveBranches}
provisioningError={provisioningError} defaultProjectPath={data.config.projectPath}
clearProvisioningError={clearProvisioningError} provisioningError={provisioningError}
activeTeams={activeTeamsForLaunch} clearProvisioningError={clearProvisioningError}
onClose={closeLaunchDialog} activeTeams={activeTeamsForLaunch}
onLaunch={handleLaunchDialogSubmit} onClose={closeLaunchDialog}
onRelaunch={handleRelaunchDialogSubmit} onLaunch={handleLaunchDialogSubmit}
/> 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,35 +737,39 @@ export const TeamListView = memo((): React.JSX.Element => {
} }
const createDialogElement = ( const createDialogElement = (
<CreateTeamDialog <Suspense fallback={null}>
open={showCreateDialog} <CreateTeamDialog
canCreate={canCreate} open={showCreateDialog}
provisioningErrorsByTeam={provisioningErrorByTeam} canCreate={canCreate}
clearProvisioningError={clearProvisioningError} provisioningErrorsByTeam={provisioningErrorByTeam}
existingTeamNames={teams.map((t) => t.teamName)} clearProvisioningError={clearProvisioningError}
provisioningTeamNames={provisioningTeamNames} existingTeamNames={teams.map((t) => t.teamName)}
activeTeams={activeTeams} provisioningTeamNames={provisioningTeamNames}
initialData={copyData ?? undefined} activeTeams={activeTeams}
defaultProjectPath={currentProjectPath} initialData={copyData ?? undefined}
onClose={handleCreateDialogClose} defaultProjectPath={currentProjectPath}
onCreate={handleCreateSubmit} onClose={handleCreateDialogClose}
onOpenTeam={openTeamTab} onCreate={handleCreateSubmit}
/> onOpenTeam={openTeamTab}
/>
</Suspense>
); );
const launchDialogElement = ( const launchDialogElement = (
<LaunchTeamDialog <Suspense fallback={null}>
mode="launch" <LaunchTeamDialog
open={launchDialogOpen} mode="launch"
teamName={launchDialogTeamName} open={launchDialogOpen}
members={launchDialogMembers} teamName={launchDialogTeamName}
defaultProjectPath={launchDialogDefaultPath} members={launchDialogMembers}
provisioningError={provisioningErrorByTeam[launchDialogTeamName] ?? null} defaultProjectPath={launchDialogDefaultPath}
clearProvisioningError={clearProvisioningError} provisioningError={provisioningErrorByTeam[launchDialogTeamName] ?? null}
activeTeams={activeTeams} clearProvisioningError={clearProvisioningError}
onClose={() => setLaunchDialogOpen(false)} activeTeams={activeTeams}
onLaunch={handleLaunchSubmit} onClose={() => setLaunchDialogOpen(false)}
/> 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,13 +307,15 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
)} )}
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<LaunchTeamDialog <Suspense fallback={null}>
mode="schedule" <LaunchTeamDialog
open={dialogOpen} mode="schedule"
teamName={teamName} open={dialogOpen}
schedule={editingSchedule} teamName={teamName}
onClose={handleClose} schedule={editingSchedule}
/> onClose={handleClose}
/>
</Suspense>
</div> </div>
); );
}; };