diff --git a/apps/mobile/src/features/threads/ThreadFeed.tsx b/apps/mobile/src/features/threads/ThreadFeed.tsx index 77729684a792e..f9195409d095f 100644 --- a/apps/mobile/src/features/threads/ThreadFeed.tsx +++ b/apps/mobile/src/features/threads/ThreadFeed.tsx @@ -1734,6 +1734,33 @@ function ThreadFeedPlaceholder(props: { ); } +// A hook's return value is frozen to the compiler, so ThreadFeed's later +// hooks can't drag this derivation into an uncached scope. +function useThreadFeedPresentation( + props: ThreadFeedProps, + expandedTurnIds: ReadonlySet, + expandedWorkGroups: Record, +) { + const [feedCache] = useState(createThreadFeedCache); + return appendPendingThreadMessages( + deriveThreadFeedPresentation( + props.feed, + props.latestRun, + expandedTurnIds, + new Set( + Object.entries(expandedWorkGroups) + .filter(([, expanded]) => expanded) + .map(([groupId]) => groupId), + ), + props.activeWorkStartedAt, + props.runlessWorkActive ?? false, + feedCache, + ), + props.feed, + props.queuedMessages, + ); +} + export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { const { listRef, onEndFollowEnabledChange, onHeaderMaterialVisibilityChange } = props; const workflowOpening = useWorkflowOpening(props.environmentId, props.threadId); @@ -1759,7 +1786,6 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { const disclosureSettleFrameRef = useRef(null); const disclosureSettleSecondFrameRef = useRef(null); const disclosureAnchorKeyRef = useRef(null); - const [feedCache] = useState(createThreadFeedCache); const previousLatestTurnRef = useRef(props.latestRun); const userScrollSettleTimerRef = useRef | null>(null); const headerMaterialVisibleRef = useRef(false); @@ -2315,23 +2341,7 @@ export const ThreadFeed = memo(function ThreadFeed(props: ThreadFeedProps) { reportHeaderMaterialVisibility(false); }, [feedThreadKey, reportHeaderMaterialVisibility]); - const presentedFeed = appendPendingThreadMessages( - deriveThreadFeedPresentation( - props.feed, - props.latestRun, - expandedTurnIds, - new Set( - Object.entries(expandedWorkGroups) - .filter(([, expanded]) => expanded) - .map(([groupId]) => groupId), - ), - props.activeWorkStartedAt, - props.runlessWorkActive ?? false, - feedCache, - ), - props.feed, - props.queuedMessages, - ); + const presentedFeed = useThreadFeedPresentation(props, expandedTurnIds, expandedWorkGroups); const setupAnchorIndex = presentedFeed.findIndex( (entry) => entry.type === "message" && entry.message.role === "user", );