History

style

Julius Marminge committed 8 months ago

Commit da300701 parent ede96ce1 file · +56 −9Browse files

Unified · Split

apps/web/src/components/ChatView.tsx+56−9
@@ -637,6 +637,11 @@ export default function ChatView({ threadId }: ChatViewProps) { · 636 unmodified lines
637637 const lastTouchClientYRef = useRef<number | null>(null);
638638 const pendingUserScrollUpIntentRef = useRef(false);
639639 const pendingAutoScrollFrameRef = useRef<number | null>(null);
640+ const pendingInteractionAnchorRef = useRef<{
641+ element: HTMLElement;
642+ top: number;
643+ } | null>(null);
644+ const pendingInteractionAnchorFrameRef = useRef<number | null>(null);
640645 const composerEditorRef = useRef<ComposerPromptEditorHandle>(null);
641646 const composerFormRef = useRef<HTMLFormElement>(null);
642647 const composerFormHeightRef = useRef(0);
@@ -1055,7 +1060,8 @@ export default function ChatView({ threadId }: ChatViewProps) { · 412 unmodified lines
10551060 return [...serverMessagesWithPreviewHandoff, ...pendingMessages];
10561061 }, [serverMessages, attachmentPreviewHandoffByMessageId, optimisticUserMessages]);
10571062 const timelineEntries = useMemo(
1058− () => deriveTimelineEntries(timelineMessages, activeThread?.proposedPlans ?? [], workLogEntries),
1063+ () =>
1064+ deriveTimelineEntries(timelineMessages, activeThread?.proposedPlans ?? [], workLogEntries),
10591065 [activeThread?.proposedPlans, timelineMessages, workLogEntries],
10601066 );
10611067 const { turnDiffSummaries, inferredCheckpointTurnCountByTurnId } =
@@ -1675,6 +1681,12 @@ export default function ChatView({ threadId }: ChatViewProps) { · 613 unmodified lines
16751681 pendingAutoScrollFrameRef.current = null;
16761682 window.cancelAnimationFrame(pendingFrame);
16771683 }, []);
1684+ const cancelPendingInteractionAnchorAdjustment = useCallback(() => {
1685+ const pendingFrame = pendingInteractionAnchorFrameRef.current;
1686+ if (pendingFrame === null) return;
1687+ pendingInteractionAnchorFrameRef.current = null;
1688+ window.cancelAnimationFrame(pendingFrame);
1689+ }, []);
16781690 const scheduleStickToBottom = useCallback(() => {
16791691 if (pendingAutoScrollFrameRef.current !== null) return;
16801692 pendingAutoScrollFrameRef.current = window.requestAnimationFrame(() => {
@@ -1682,6 +1694,40 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1 unmodified line
16821694 scrollMessagesToBottom();
16831695 });
16841696 }, [scrollMessagesToBottom]);
1697+ const onMessagesClickCapture = useCallback(
1698+ (event: React.MouseEvent<HTMLDivElement>) => {
1699+ const scrollContainer = messagesScrollRef.current;
1700+ if (!scrollContainer || !(event.target instanceof Element)) return;
1701+
1702+ const trigger = event.target.closest<HTMLElement>(
1703+ "button, summary, [role='button'], [data-scroll-anchor-target]",
1704+ );
1705+ if (!trigger || !scrollContainer.contains(trigger)) return;
1706+
1707+ pendingInteractionAnchorRef.current = {
1708+ element: trigger,
1709+ top: trigger.getBoundingClientRect().top,
1710+ };
1711+
1712+ cancelPendingInteractionAnchorAdjustment();
1713+ pendingInteractionAnchorFrameRef.current = window.requestAnimationFrame(() => {
1714+ pendingInteractionAnchorFrameRef.current = null;
1715+ const anchor = pendingInteractionAnchorRef.current;
1716+ pendingInteractionAnchorRef.current = null;
1717+ const activeScrollContainer = messagesScrollRef.current;
1718+ if (!anchor || !activeScrollContainer) return;
1719+ if (!anchor.element.isConnected || !activeScrollContainer.contains(anchor.element)) return;
1720+
1721+ const nextTop = anchor.element.getBoundingClientRect().top;
1722+ const delta = nextTop - anchor.top;
1723+ if (Math.abs(delta) < 0.5) return;
1724+
1725+ activeScrollContainer.scrollTop += delta;
1726+ lastKnownScrollTopRef.current = activeScrollContainer.scrollTop;
1727+ });
1728+ },
1729+ [cancelPendingInteractionAnchorAdjustment],
1730+ );
16851731 const forceStickToBottom = useCallback(() => {
16861732 cancelPendingStickToBottom();
16871733 scrollMessagesToBottom();
@@ -1751,8 +1797,9 @@ export default function ChatView({ threadId }: ChatViewProps) { · 63 unmodified lines
17511797 useEffect(() => {
17521798 return () => {
17531799 cancelPendingStickToBottom();
1800+ cancelPendingInteractionAnchorAdjustment();
17541801 };
1755− }, [cancelPendingStickToBottom]);
1802+ }, [cancelPendingInteractionAnchorAdjustment, cancelPendingStickToBottom]);
17561803 useLayoutEffect(() => {
17571804 if (!activeThread?.id) return;
17581805 shouldAutoScrollRef.current = true;
@@ -2279,7 +2326,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 520 unmodified lines
22792326 if (showPlanFollowUpPrompt && activeProposedPlan) {
22802327 const followUpText =
22812328 trimmed.length > 0
2282− ? trimmed
2329+ ? trimmed
22832330 : buildPlanImplementationPrompt(activeProposedPlan.planMarkdown);
22842331 const nextInteractionMode = trimmed.length > 0 ? "plan" : "default";
22852332 promptRef.current = "";
@@ -3323,6 +3370,7 @@ export default function ChatView({ threadId }: ChatViewProps) { · 1037 unmodified lines
33233370 ref={setMessagesScrollContainerRef}
33243371 className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-y-contain px-3 py-3 sm:px-5 sm:py-4"
33253372 onScroll={onMessagesScroll}
3373+ onClickCapture={onMessagesClickCapture}
33263374 onWheel={onMessagesWheel}
33273375 onPointerDown={onMessagesPointerDown}
33283376 onPointerUp={onMessagesPointerUp}
@@ -4270,15 +4318,14 @@ const ComposerPlanFollowUpBanner = memo(function ComposerPlanFollowUpBanner({ · 941 unmodified lines
42704318 return (
42714319 <div className="px-4 py-3.5 sm:px-5 sm:py-4">
42724320 <div className="flex flex-wrap items-center gap-2">
4273− <Badge variant="secondary">Plan ready</Badge>
4321+ <span className="uppercase text-sm tracking-[0.2em]">Plan ready</span>
42744322 {planTitle ? (
4275− <span className="min-w-0 flex-1 truncate text-xs text-muted-foreground">{planTitle}</span>
4323+ <span className="min-w-0 flex-1 truncate text-sm font-medium">{planTitle}</span>
42764324 ) : null}
42774325 </div>
4278− <div className="mt-2 text-xs text-muted-foreground">
4279− Leave the composer empty to implement here, use the menu to implement in a new thread, or
4280− type feedback to refine it.
4281− </div>
4326+ {/* <div className="mt-2 text-xs text-muted-foreground">
4327+ Review the plan
4328+ </div> */}
42824329 </div>
42834330 );
42844331 });