History

Restore lost useMemo calls in ChatView and fix threadActivities referential stability

Revert bare function calls / IIFEs back to useMemo for: modelOptions, workLogEntries, latestTurnHasToolActivity, pendingApprovals, timelineEntries, revertTurnCountByUserMessageId, completionSummary, and completionDividerBeforeEntryId. Add EMPTY_ACTIVITIES module-level constant so the ?? [] fallback doesn't create a new array reference every render, which was defeating memoization.

Julius Marminge committed 8 months ago

Commit fd4ff8c1 parent 1bcfbdf1 file · +38 −16Browse files

Unified · Split

apps/web/src/components/ChatView.tsx+38−16
@@ -19,6 +19,7 @@ import { · 18 unmodified lines
1919 type TurnId,
2020 normalizeModelSlug,
2121 resolveModelSlug,
22+ OrchestrationThreadActivity,
2223 } from "@t3tools/contracts";
2324 import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2425 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -140,6 +141,7 @@ const ATTACHMENT_PREVIEW_HANDOFF_TTL_MS = 5000; · 115 unmodified lines
140141 const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`;
141142 const IMAGE_ONLY_BOOTSTRAP_PROMPT =
142143 "[User attached one or more images without additional text. Respond using the conversation context and the attached image(s).]";
144+const EMPTY_ACTIVITIES: OrchestrationThreadActivity[] = [];
143145 const EMPTY_KEYBINDINGS: ResolvedKeybindingsConfig = [];
144146 const EMPTY_PROJECT_ENTRIES: ProjectEntry[] = [];
145147 const COMPOSER_PATH_QUERY_DEBOUNCE_MS = 120;
@@ -611,22 +613,28 @@ export default function ChatView({ threadId }: ChatViewProps) { · 465 unmodified lines
611613 );
612614 const selectedModel = resolveModelSlug(composerDraft.model ?? baseThreadModel);
613615 const selectedEffort = composerDraft.effort ?? DEFAULT_REASONING;
614− const modelOptions = getAppModelOptions(settings.customCodexModels, selectedModel);
616+ const modelOptions = useMemo(
617+ () => getAppModelOptions(settings.customCodexModels, selectedModel),
618+ [selectedModel, settings.customCodexModels],
619+ );
615620 const phase = derivePhase(activeThread?.session ?? null);
616621 const isSendBusy = sendPhase !== "idle";
617622 const isPreparingWorktree = sendPhase === "preparing-worktree";
618623 const isWorking = phase === "running" || isSendBusy || isConnecting || isRevertingCheckpoint;
619624 const nowIso = new Date(nowTick).toISOString();
620− const threadActivities = activeThread?.activities ?? [];
621− const workLogEntries = deriveWorkLogEntries(
622− threadActivities,
623− activeLatestTurn?.turnId ?? undefined,
625+ const threadActivities = activeThread?.activities ?? EMPTY_ACTIVITIES;
626+ const workLogEntries = useMemo(
627+ () => deriveWorkLogEntries(threadActivities, activeLatestTurn?.turnId ?? undefined),
628+ [activeLatestTurn?.turnId, threadActivities],
629+ );
630+ const latestTurnHasToolActivity = useMemo(
631+ () => hasToolActivityForTurn(threadActivities, activeLatestTurn?.turnId),
632+ [activeLatestTurn?.turnId, threadActivities],
624633 );
625− const latestTurnHasToolActivity = hasToolActivityForTurn(
626− threadActivities,
627− activeLatestTurn?.turnId,
634+ const pendingApprovals = useMemo(
635+ () => derivePendingApprovals(threadActivities),
636+ [threadActivities],
628637 );
629− const pendingApprovals = derivePendingApprovals(threadActivities);
630638 useEffect(() => {
631639 attachmentPreviewHandoffByMessageIdRef.current = attachmentPreviewHandoffByMessageId;
632640 }, [attachmentPreviewHandoffByMessageId]);
@@ -744,7 +752,10 @@ export default function ChatView({ threadId }: ChatViewProps) { · 111 unmodified lines
744752 }
745753 return [...serverMessagesWithPreviewHandoff, ...pendingMessages];
746754 }, [serverMessages, attachmentPreviewHandoffByMessageId, optimisticUserMessages]);
747− const timelineEntries = deriveTimelineEntries(timelineMessages, workLogEntries);
755+ const timelineEntries = useMemo(
756+ () => deriveTimelineEntries(timelineMessages, workLogEntries),
757+ [timelineMessages, workLogEntries],
758+ );
748759 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
749760 useTurnDiffSummaries(activeThread);
750761 const turnDiffSummaryByAssistantMessageId = useMemo(() => {
@@ -755,7 +766,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 4 unmodified lines
755766 }
756767 return byMessageId;
757768 }, [turnDiffSummaries]);
758− const revertTurnCountByUserMessageId = (() => {
769+ const revertTurnCountByUserMessageId = useMemo(() => {
759770 const byUserMessageId = new Map<MessageId, number>();
760771 for (let index = 0; index < timelineEntries.length; index += 1) {
761772 const entry = timelineEntries[index];
@@ -786,9 +797,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 24 unmodified lines
786797 }
787798
788799 return byUserMessageId;
789− })();
800+ }, [inferredCheckpointTurnCountByTurnId, timelineEntries, turnDiffSummaryByAssistantMessageId]);
790801
791− const completionSummary = (() => {
802+ const completionSummary = useMemo(() => {
792803 if (!latestTurnSettled) return null;
793804 if (!activeLatestTurn?.startedAt) return null;
794805 if (!activeLatestTurn.completedAt) return null;
@@ -796,8 +807,13 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1 unmodified line
796807
797808 const elapsed = formatElapsed(activeLatestTurn.startedAt, activeLatestTurn.completedAt);
798809 return elapsed ? `Worked for ${elapsed}` : null;
799− })();
800− const completionDividerBeforeEntryId = (() => {
810+ }, [
811+ activeLatestTurn?.completedAt,
812+ activeLatestTurn?.startedAt,
813+ latestTurnHasToolActivity,
814+ latestTurnSettled,
815+ ]);
816+ const completionDividerBeforeEntryId = useMemo(() => {
801817 if (!latestTurnSettled) return null;
802818 if (!activeLatestTurn?.startedAt) return null;
803819 if (!activeLatestTurn.completedAt) return null;
@@ -821,7 +837,13 @@ export default function ChatView({ threadId }: ChatViewProps) { · 17 unmodified lines
821837 }
822838 }
823839 return inRangeMatch ?? fallbackMatch;
824− })();
840+ }, [
841+ activeLatestTurn?.completedAt,
842+ activeLatestTurn?.startedAt,
843+ completionSummary,
844+ latestTurnSettled,
845+ timelineEntries,
846+ ]);
825847 const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null;
826848 const composerTrigger = useMemo(
827849 () => detectComposerTrigger(prompt, composerCursor),

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t