perf: lazy-load TaskDetailDialog, SendMessageDialog, CreateTaskDialog, ChangeReviewDialog

Defers parsing 4575 lines of heavy dialog code until first use, reducing
initial bundle parse time for TeamDetailView.
This commit is contained in:
Mike 2026-05-02 22:24:03 +05:00
parent 5d6667b23d
commit 5cd890dcdd

View file

@ -78,12 +78,9 @@ import {
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { AddMemberDialog } from './dialogs/AddMemberDialog'; import { AddMemberDialog } from './dialogs/AddMemberDialog';
import { CreateTaskDialog } from './dialogs/CreateTaskDialog';
import { EditTeamDialog } from './dialogs/EditTeamDialog'; import { EditTeamDialog } from './dialogs/EditTeamDialog';
import 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 { TaskDetailDialog } from './dialogs/TaskDetailDialog';
import { executeTeamRelaunch } from './dialogs/teamRelaunchFlow'; import { executeTeamRelaunch } from './dialogs/teamRelaunchFlow';
import { KanbanBoard } from './kanban/KanbanBoard'; import { KanbanBoard } from './kanban/KanbanBoard';
import { UNASSIGNED_OWNER } from './kanban/KanbanFilterPopover'; import { UNASSIGNED_OWNER } from './kanban/KanbanFilterPopover';
@ -107,9 +104,20 @@ const TeamGraphOverlay = lazy(() =>
default: m.TeamGraphOverlay, default: m.TeamGraphOverlay,
})) }))
); );
const TaskDetailDialog = lazy(() =>
import('./dialogs/TaskDetailDialog').then((m) => ({ default: m.TaskDetailDialog }))
);
const SendMessageDialog = lazy(() =>
import('./dialogs/SendMessageDialog').then((m) => ({ default: m.SendMessageDialog }))
);
const CreateTaskDialog = lazy(() =>
import('./dialogs/CreateTaskDialog').then((m) => ({ default: m.CreateTaskDialog }))
);
const ChangeReviewDialog = lazy(() =>
import('./review/ChangeReviewDialog').then((m) => ({ default: m.ChangeReviewDialog }))
);
import { MemberList } from './members/MemberList'; import { MemberList } from './members/MemberList';
import { MessagesPanel } from './messages/MessagesPanel'; import { MessagesPanel } from './messages/MessagesPanel';
import { ChangeReviewDialog } from './review/ChangeReviewDialog';
import { ScheduleSection } from './schedule/ScheduleSection'; import { ScheduleSection } from './schedule/ScheduleSection';
import { TeamSidebarHost } from './sidebar/TeamSidebarHost'; import { TeamSidebarHost } from './sidebar/TeamSidebarHost';
import { TeamSidebarPortalSource } from './sidebar/TeamSidebarPortalSource'; import { TeamSidebarPortalSource } from './sidebar/TeamSidebarPortalSource';
@ -2857,21 +2865,23 @@ export const TeamDetailView = memo(
}} }}
/> />
<CreateTaskDialog <Suspense fallback={null}>
open={createTaskDialog.open} <CreateTaskDialog
teamName={teamName} open={createTaskDialog.open}
members={activeMembers} teamName={teamName}
tasks={data.tasks} members={activeMembers}
isTeamAlive={data.isAlive && !isTeamProvisioning} tasks={data.tasks}
defaultSubject={createTaskDialog.defaultSubject} isTeamAlive={data.isAlive && !isTeamProvisioning}
defaultDescription={createTaskDialog.defaultDescription} defaultSubject={createTaskDialog.defaultSubject}
defaultOwner={createTaskDialog.defaultOwner} defaultDescription={createTaskDialog.defaultDescription}
defaultStartImmediately={createTaskDialog.defaultStartImmediately} defaultOwner={createTaskDialog.defaultOwner}
defaultChip={createTaskDialog.defaultChip} defaultStartImmediately={createTaskDialog.defaultStartImmediately}
onClose={closeCreateTaskDialog} defaultChip={createTaskDialog.defaultChip}
onSubmit={handleCreateTask} onClose={closeCreateTaskDialog}
submitting={creatingTask} onSubmit={handleCreateTask}
/> submitting={creatingTask}
/>
</Suspense>
<EditTeamDialog <EditTeamDialog
open={editDialogOpen} open={editDialogOpen}
@ -2997,103 +3007,107 @@ export const TeamDetailView = memo(
/> />
</Suspense> </Suspense>
<SendMessageDialog <Suspense fallback={null}>
open={sendDialogOpen} <SendMessageDialog
teamName={teamName} open={sendDialogOpen}
members={activeMembers} teamName={teamName}
defaultRecipient={sendDialogRecipient} members={activeMembers}
defaultText={sendDialogDefaultText} defaultRecipient={sendDialogRecipient}
defaultChip={sendDialogDefaultChip} defaultText={sendDialogDefaultText}
quotedMessage={replyQuote} defaultChip={sendDialogDefaultChip}
isTeamAlive={data.isAlive} quotedMessage={replyQuote}
sending={sendingMessage} isTeamAlive={data.isAlive}
sendError={sendMessageError} sending={sendingMessage}
sendWarning={sendMessageWarning} sendError={sendMessageError}
sendDebugDetails={sendMessageDebugDetails} sendWarning={sendMessageWarning}
lastResult={lastSendMessageResult} sendDebugDetails={sendMessageDebugDetails}
onSend={async (member, text, summary, attachments, actionMode, taskRefs) => { lastResult={lastSendMessageResult}
const sentAtMs = Date.now(); onSend={async (member, text, summary, attachments, actionMode, taskRefs) => {
setPendingRepliesByMember((prev) => ({ ...prev, [member]: sentAtMs })); const sentAtMs = Date.now();
try { setPendingRepliesByMember((prev) => ({ ...prev, [member]: sentAtMs }));
const result = await sendTeamMessage(teamName, { try {
member, const result = await sendTeamMessage(teamName, {
text, member,
summary, text,
attachments, summary,
actionMode, attachments,
taskRefs, actionMode,
}); taskRefs,
if ( });
result?.runtimeDelivery?.attempted === true && if (
result.runtimeDelivery.delivered === false result?.runtimeDelivery?.attempted === true &&
) { result.runtimeDelivery.delivered === false
) {
setPendingRepliesByMember((prev) => {
if (prev[member] !== sentAtMs) return prev;
const next = { ...prev };
delete next[member];
return next;
});
}
return result;
} catch (error) {
setPendingRepliesByMember((prev) => { setPendingRepliesByMember((prev) => {
if (prev[member] !== sentAtMs) return prev; if (prev[member] !== sentAtMs) return prev;
const next = { ...prev }; const next = { ...prev };
delete next[member]; delete next[member];
return next; return next;
}); });
throw error;
} }
return result; }}
} catch (error) { onClose={() => {
setPendingRepliesByMember((prev) => { setSendDialogOpen(false);
if (prev[member] !== sentAtMs) return prev; setReplyQuote(undefined);
const next = { ...prev }; setSendDialogDefaultText(undefined);
delete next[member]; setSendDialogDefaultChip(undefined);
return next; }}
}); />
throw error; </Suspense>
}
}}
onClose={() => {
setSendDialogOpen(false);
setReplyQuote(undefined);
setSendDialogDefaultText(undefined);
setSendDialogDefaultChip(undefined);
}}
/>
<TaskDetailDialog <Suspense fallback={null}>
open={selectedTask !== null} <TaskDetailDialog
task={selectedTask} open={selectedTask !== null}
teamName={teamName} task={selectedTask}
kanbanTaskState={ teamName={teamName}
selectedTask ? data?.kanbanState.tasks[selectedTask.id] : undefined kanbanTaskState={
} selectedTask ? data?.kanbanState.tasks[selectedTask.id] : undefined
taskMap={taskMap}
members={activeMembers}
onClose={() => setSelectedTask(null)}
onScrollToTask={(taskId) => {
setSelectedTask(null);
const el = document.querySelector(`[data-task-id="${taskId}"]`);
if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
el.classList.remove('kanban-card-focus-pulse');
void (el as HTMLElement).offsetWidth;
el.classList.add('kanban-card-focus-pulse');
el.addEventListener(
'animationend',
() => el.classList.remove('kanban-card-focus-pulse'),
{ once: true }
);
} }
}} taskMap={taskMap}
onOwnerChange={(taskId, owner) => { members={activeMembers}
void (async () => { onClose={() => setSelectedTask(null)}
try { onScrollToTask={(taskId) => {
await updateTaskOwner(teamName, taskId, owner); setSelectedTask(null);
} catch { const el = document.querySelector(`[data-task-id="${taskId}"]`);
// error via store if (el) {
el.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
el.classList.remove('kanban-card-focus-pulse');
void (el as HTMLElement).offsetWidth;
el.classList.add('kanban-card-focus-pulse');
el.addEventListener(
'animationend',
() => el.classList.remove('kanban-card-focus-pulse'),
{ once: true }
);
} }
})(); }}
}} onOwnerChange={(taskId, owner) => {
onViewChanges={handleViewChangesForFile} void (async () => {
onOpenInEditor={(filePath) => { try {
const { revealFileInEditor } = useStore.getState(); await updateTaskOwner(teamName, taskId, owner);
revealFileInEditor(filePath); } catch {
}} // error via store
onDeleteTask={handleDeleteTask} }
/> })();
}}
onViewChanges={handleViewChangesForFile}
onOpenInEditor={(filePath) => {
const { revealFileInEditor } = useStore.getState();
revealFileInEditor(filePath);
}}
onDeleteTask={handleDeleteTask}
/>
</Suspense>
<TrashDialog <TrashDialog
open={trashOpen} open={trashOpen}
@ -3110,26 +3124,28 @@ export const TeamDetailView = memo(
}} }}
/> />
<ChangeReviewDialog <Suspense fallback={null}>
open={reviewDialogState.open} <ChangeReviewDialog
onOpenChange={(open) => open={reviewDialogState.open}
setReviewDialogState((prev) => ({ onOpenChange={(open) =>
...prev, setReviewDialogState((prev) => ({
open, ...prev,
...(open open,
? {} ...(open
: { initialFilePath: undefined, taskChangeRequestOptions: undefined }), ? {}
})) : { initialFilePath: undefined, taskChangeRequestOptions: undefined }),
} }))
teamName={teamName} }
mode={reviewDialogState.mode} teamName={teamName}
memberName={reviewDialogState.memberName} mode={reviewDialogState.mode}
taskId={reviewDialogState.taskId} memberName={reviewDialogState.memberName}
initialFilePath={reviewDialogState.initialFilePath} taskId={reviewDialogState.taskId}
taskChangeRequestOptions={reviewDialogState.taskChangeRequestOptions} initialFilePath={reviewDialogState.initialFilePath}
projectPath={data.config.projectPath} taskChangeRequestOptions={reviewDialogState.taskChangeRequestOptions}
onEditorAction={handleEditorAction} projectPath={data.config.projectPath}
/> onEditorAction={handleEditorAction}
/>
</Suspense>
</div> </div>
<div <div
ref={setMessagesPanelMountPoint} ref={setMessagesPanelMountPoint}