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 { Input } from '@renderer/components/ui/input';
@ -24,8 +24,11 @@ import {
} from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { LaunchTeamDialog } from '../team/dialogs/LaunchTeamDialog';
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 { ScheduleStatusBadge } from '../team/schedule/ScheduleStatusBadge';
@ -562,13 +565,15 @@ export const SchedulesView = (): React.JSX.Element => {
</div>
{/* Create/Edit Dialog */}
<LaunchTeamDialog
mode="schedule"
open={dialogOpen}
teamName={editingSchedule?.teamName}
schedule={editingSchedule}
onClose={handleClose}
/>
<Suspense fallback={null}>
<LaunchTeamDialog
mode="schedule"
open={dialogOpen}
teamName={editingSchedule?.teamName}
schedule={editingSchedule}
onClose={handleClose}
/>
</Suspense>
</div>
);
};

View file

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

View file

@ -30,7 +30,6 @@ interface SessionItemProps {
isHidden?: boolean;
multiSelectActive?: boolean;
isSelected?: boolean;
onToggleSelect?: () => void;
}
/**
@ -164,7 +163,6 @@ export const SessionItem = memo(
isHidden,
multiSelectActive,
isSelected,
onToggleSelect,
}: Readonly<SessionItemProps>): React.JSX.Element => {
const {
openTab,
@ -174,6 +172,7 @@ export const SessionItem = memo(
splitPane,
togglePinSession,
toggleHideSession,
toggleSidebarSessionSelection,
} = useStore(
useShallow((s) => ({
openTab: s.openTab,
@ -183,6 +182,7 @@ export const SessionItem = memo(
splitPane: s.splitPane,
togglePinSession: s.togglePinSession,
toggleHideSession: s.toggleHideSession,
toggleSidebarSessionSelection: s.toggleSidebarSessionSelection,
}))
);
@ -192,8 +192,8 @@ export const SessionItem = memo(
if (!activeProjectId) return;
// In multi-select mode, clicks toggle selection
if (multiSelectActive && onToggleSelect) {
onToggleSelect();
if (multiSelectActive) {
toggleSidebarSessionSelection(session.id);
return;
}
@ -291,7 +291,7 @@ export const SessionItem = memo(
<input
type="checkbox"
checked={isSelected ?? false}
onChange={() => onToggleSelect?.()}
onChange={() => toggleSidebarSessionSelection(session.id)}
onClick={(e) => e.stopPropagation()}
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 { CreateTaskDialog } from './dialogs/CreateTaskDialog';
import { EditTeamDialog } from './dialogs/EditTeamDialog';
import { LaunchTeamDialog, type TeamLaunchDialogMode } from './dialogs/LaunchTeamDialog';
import type { TeamLaunchDialogMode } from './dialogs/LaunchTeamDialog';
import { ReviewDialog } from './dialogs/ReviewDialog';
import { SendMessageDialog } from './dialogs/SendMessageDialog';
import { TaskDetailDialog } from './dialogs/TaskDetailDialog';
@ -96,6 +96,9 @@ import type { AddMemberEntry } from './dialogs/AddMemberDialog';
import type { TeamMessagesPanelMode } from '@renderer/types/teamMessagesPanelMode';
import type { ComponentProps, CSSProperties } from 'react';
const LaunchTeamDialog = lazy(() =>
import('./dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
const ProjectEditorOverlay = lazy(() =>
import('./editor/ProjectEditorOverlay').then((m) => ({ default: m.ProjectEditorOverlay }))
);
@ -2176,18 +2179,20 @@ export const TeamDetailView = memo(
</div>
</div>
</div>
<LaunchTeamDialog
mode={launchDialogState.mode}
open={launchDialogOpen}
teamName={teamName}
members={[]}
defaultProjectPath={draftTeamSummary?.projectPath}
provisioningError={provisioningError}
clearProvisioningError={clearProvisioningError}
onClose={closeLaunchDialog}
onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit}
/>
<Suspense fallback={null}>
<LaunchTeamDialog
mode={launchDialogState.mode}
open={launchDialogOpen}
teamName={teamName}
members={[]}
defaultProjectPath={draftTeamSummary?.projectPath}
provisioningError={provisioningError}
clearProvisioningError={clearProvisioningError}
onClose={closeLaunchDialog}
onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit}
/>
</Suspense>
</>
);
}
@ -2976,19 +2981,21 @@ export const TeamDetailView = memo(
</DialogContent>
</Dialog>
<LaunchTeamDialog
mode={launchDialogState.mode}
open={launchDialogOpen}
teamName={teamName}
members={membersWithLiveBranches}
defaultProjectPath={data.config.projectPath}
provisioningError={provisioningError}
clearProvisioningError={clearProvisioningError}
activeTeams={activeTeamsForLaunch}
onClose={closeLaunchDialog}
onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit}
/>
<Suspense fallback={null}>
<LaunchTeamDialog
mode={launchDialogState.mode}
open={launchDialogOpen}
teamName={teamName}
members={membersWithLiveBranches}
defaultProjectPath={data.config.projectPath}
provisioningError={provisioningError}
clearProvisioningError={clearProvisioningError}
activeTeams={activeTeamsForLaunch}
onClose={closeLaunchDialog}
onLaunch={handleLaunchDialogSubmit}
onRelaunch={handleRelaunchDialogSubmit}
/>
</Suspense>
<SendMessageDialog
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 { api, isElectronMode } from '@renderer/api';
@ -45,8 +45,7 @@ import {
} from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { CreateTeamDialog } from './dialogs/CreateTeamDialog';
import { LaunchTeamDialog } from './dialogs/LaunchTeamDialog';
import type { ActiveTeamRef, TeamCopyData } from './dialogs/CreateTeamDialog';
import { TeamEmptyState } from './TeamEmptyState';
import { EMPTY_TEAM_FILTER, TeamListFilterPopover } from './TeamListFilterPopover';
import {
@ -55,7 +54,13 @@ import {
teamMatchesProjectSelection,
} 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 { TeamStatus } from '@renderer/utils/teamListStatus';
import type {
@ -732,35 +737,39 @@ export const TeamListView = memo((): React.JSX.Element => {
}
const createDialogElement = (
<CreateTeamDialog
open={showCreateDialog}
canCreate={canCreate}
provisioningErrorsByTeam={provisioningErrorByTeam}
clearProvisioningError={clearProvisioningError}
existingTeamNames={teams.map((t) => t.teamName)}
provisioningTeamNames={provisioningTeamNames}
activeTeams={activeTeams}
initialData={copyData ?? undefined}
defaultProjectPath={currentProjectPath}
onClose={handleCreateDialogClose}
onCreate={handleCreateSubmit}
onOpenTeam={openTeamTab}
/>
<Suspense fallback={null}>
<CreateTeamDialog
open={showCreateDialog}
canCreate={canCreate}
provisioningErrorsByTeam={provisioningErrorByTeam}
clearProvisioningError={clearProvisioningError}
existingTeamNames={teams.map((t) => t.teamName)}
provisioningTeamNames={provisioningTeamNames}
activeTeams={activeTeams}
initialData={copyData ?? undefined}
defaultProjectPath={currentProjectPath}
onClose={handleCreateDialogClose}
onCreate={handleCreateSubmit}
onOpenTeam={openTeamTab}
/>
</Suspense>
);
const launchDialogElement = (
<LaunchTeamDialog
mode="launch"
open={launchDialogOpen}
teamName={launchDialogTeamName}
members={launchDialogMembers}
defaultProjectPath={launchDialogDefaultPath}
provisioningError={provisioningErrorByTeam[launchDialogTeamName] ?? null}
clearProvisioningError={clearProvisioningError}
activeTeams={activeTeams}
onClose={() => setLaunchDialogOpen(false)}
onLaunch={handleLaunchSubmit}
/>
<Suspense fallback={null}>
<LaunchTeamDialog
mode="launch"
open={launchDialogOpen}
teamName={launchDialogTeamName}
members={launchDialogMembers}
defaultProjectPath={launchDialogDefaultPath}
provisioningError={provisioningErrorByTeam[launchDialogTeamName] ?? null}
clearProvisioningError={clearProvisioningError}
activeTeams={activeTeams}
onClose={() => setLaunchDialogOpen(false)}
onLaunch={handleLaunchSubmit}
/>
</Suspense>
);
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 { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover';
@ -18,9 +18,11 @@ import {
} from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { LaunchTeamDialog } from '../dialogs/LaunchTeamDialog';
import { ScheduleEmptyState } from './ScheduleEmptyState';
const LaunchTeamDialog = lazy(() =>
import('../dialogs/LaunchTeamDialog').then((m) => ({ default: m.LaunchTeamDialog }))
);
import { ScheduleRunLogDialog } from './ScheduleRunLogDialog';
import { ScheduleRunRow } from './ScheduleRunRow';
import { ScheduleStatusBadge } from './ScheduleStatusBadge';
@ -305,13 +307,15 @@ export const ScheduleSection = ({ teamName }: ScheduleSectionProps): React.JSX.E
)}
{/* Create/Edit Dialog */}
<LaunchTeamDialog
mode="schedule"
open={dialogOpen}
teamName={teamName}
schedule={editingSchedule}
onClose={handleClose}
/>
<Suspense fallback={null}>
<LaunchTeamDialog
mode="schedule"
open={dialogOpen}
teamName={teamName}
schedule={editingSchedule}
onClose={handleClose}
/>
</Suspense>
</div>
);
};