Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 83828b9

Browse files
Revert "Add user-message marker rail to conversation scrollbar" (#3753)
Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com>
1 parent 285ec6d commit 83828b9

9 files changed

Lines changed: 24 additions & 931 deletions

File tree

‎apps/code/snapshots.yml‎

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -344,14 +344,6 @@ snapshots:
344344
hash: v1.k4693efd2.602ee87d6d7a745d4b09d9e49bd12a70e52299b78ca97a48b5ca73a01532d801.F_1XKDzCxzMm_opEt9FvzczBb_2n-vyvOyGFXHGJmTk
345345
features-sessions-conversationview--with-pending-prompt--light:
346346
hash: v1.k4693efd2.1636dfc3ee0dd9d28065166f2fff0193f2d499692c74749d3afa7585aae898b5.YxTPa4CsqI2YuGLUdck8340ITZQrpna_C6z-SgII4Ws
347-
features-sessions-scrollbarrail--pure--dark:
348-
hash: v1.k4693efd2.6baec21b1942848a3a376d2b90ca106b2c1b94de74382b4cd05673695d2649ee.UDDPtLDf1J2E3d3Oiy9is77Nm5xRSIfB7q5z0uUZRSQ
349-
features-sessions-scrollbarrail--pure--light:
350-
hash: v1.k4693efd2.33f02943fdbb1ea9774881f8909b580ab03ba02749614c533af1bf51dd817dca.ieP2bhDz0U_PTB4M6Zplvit0XlSTFiUn083GYBFNuC0
351-
features-sessions-scrollbarrail--scrollable-conversation--dark:
352-
hash: v1.k4693efd2.203b62b52d6365d6ec222f7529c35b3f46b17cd07ca792a8ce2de594af4d589b.3dfpnVM8yxor1PNO5aPk0pxn5THZjK4otA6mbz3XhtA
353-
features-sessions-scrollbarrail--scrollable-conversation--light:
354-
hash: v1.k4693efd2.fa7ef9cbdb09aef6108efcd3606cee32d889b3ad3b8f8be1ac637aed4e80cc72.0XcIKzM-sZHVG9hTZkAHdnBIPSrBiKmjq56QRQ6nasc
355347
features-sessions-toolcallblock--create-new-file--dark:
356348
hash: v1.k4693efd2.8847f96037682460aba47f0e0f5113037918235fc98596933f2d1d06fd6f8f92.SHetexoVOtozsZGZQMcVbmpBlbk7M5D5Q0R_TJvPnyc
357349
features-sessions-toolcallblock--create-new-file--light:

‎packages/ui/src/features/sessions/components/ConversationView.tsx‎

Lines changed: 24 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import type { CollapseMode } from "@posthog/ui/features/sessions/components/new-
3434
import { createIncrementalThreadGrouper } from "@posthog/ui/features/sessions/components/new-thread/incrementalThreadGrouping";
3535
import { ToolCallGroupChip } from "@posthog/ui/features/sessions/components/new-thread/ToolCallGroupChip";
3636
import { SessionFooter } from "@posthog/ui/features/sessions/components/SessionFooter";
37-
import { MessageScrollbarRail } from "@posthog/ui/features/sessions/components/scrollbar-rail/MessageScrollbarRail";
38-
import { useMessageRailMarkers } from "@posthog/ui/features/sessions/components/scrollbar-rail/useMessageRailMarkers";
3937
import {
4038
type RenderItem,
4139
SessionUpdateView,
@@ -75,8 +73,8 @@ import {
7573
useCallback,
7674
useEffect,
7775
useMemo,
78-
useReducer,
7976
useRef,
77+
useState,
8078
} from "react";
8179
import { useHotkeys } from "react-hotkeys-hook";
8280

@@ -108,57 +106,6 @@ export interface ConversationViewProps {
108106
promptRecallRef?: RefObject<PromptRecallHandler | null>;
109107
}
110108

111-
interface ConversationViewState {
112-
showScrollButton: boolean;
113-
jumpPickerOpen: boolean;
114-
keyboardFocusedMessageId: string | null;
115-
scrollElements: {
116-
scrollEl: HTMLElement | null;
117-
contentEl: HTMLElement | null;
118-
};
119-
}
120-
121-
type ConversationViewAction =
122-
| { type: "set-show-scroll-button"; value: boolean }
123-
| { type: "set-jump-picker-open"; value: boolean }
124-
| { type: "set-keyboard-focused-message"; value: string | null }
125-
| {
126-
type: "set-scroll-elements";
127-
value: ConversationViewState["scrollElements"];
128-
};
129-
130-
const INITIAL_CONVERSATION_VIEW_STATE: ConversationViewState = {
131-
showScrollButton: false,
132-
jumpPickerOpen: false,
133-
keyboardFocusedMessageId: null,
134-
scrollElements: { scrollEl: null, contentEl: null },
135-
};
136-
137-
function conversationViewReducer(
138-
state: ConversationViewState,
139-
action: ConversationViewAction,
140-
): ConversationViewState {
141-
switch (action.type) {
142-
case "set-show-scroll-button":
143-
return state.showScrollButton === action.value
144-
? state
145-
: { ...state, showScrollButton: action.value };
146-
case "set-jump-picker-open":
147-
return state.jumpPickerOpen === action.value
148-
? state
149-
: { ...state, jumpPickerOpen: action.value };
150-
case "set-keyboard-focused-message":
151-
return state.keyboardFocusedMessageId === action.value
152-
? state
153-
: { ...state, keyboardFocusedMessageId: action.value };
154-
case "set-scroll-elements":
155-
return state.scrollElements.scrollEl === action.value.scrollEl &&
156-
state.scrollElements.contentEl === action.value.contentEl
157-
? state
158-
: { ...state, scrollElements: action.value };
159-
}
160-
}
161-
162109
export function ConversationView({
163110
events,
164111
isPromptPending,
@@ -187,10 +134,7 @@ export function ConversationView({
187134

188135
const listRef = useRef<VirtualizedListHandle>(null);
189136
const isAtBottomRef = useRef(true);
190-
const [viewState, dispatchViewState] = useReducer(
191-
conversationViewReducer,
192-
INITIAL_CONVERSATION_VIEW_STATE,
193-
);
137+
const [showScrollButton, setShowScrollButton] = useState(false);
194138
const debugLogsCloudRuns = useSettingsStore((s) => s.debugLogsCloudRuns);
195139
const showDebugLogs = debugLogsCloudRuns;
196140

@@ -225,16 +169,14 @@ export function ConversationView({
225169
}
226170
const firstUserMessageId = firstUserMessageIdRef.current;
227171

228-
const initialItemIdsRef = useRef<Set<string> | null>(null);
229-
if (initialItemIdsRef.current === null) {
230-
initialItemIdsRef.current = new Set<string>();
231-
for (const item of conversationItems) {
232-
if (item.type === "user_message") {
233-
initialItemIdsRef.current.add(item.id);
234-
}
235-
}
236-
}
237-
const initialItemIds = initialItemIdsRef.current;
172+
const [initialItemIds] = useState(
173+
() =>
174+
new Set(
175+
conversationItems
176+
.filter((i) => i.type === "user_message")
177+
.map((i) => i.id),
178+
),
179+
);
238180

239181
const pendingPermissions = usePendingPermissionsForTask(taskId ?? "");
240182
const pendingPermissionsCount = pendingPermissions.size;
@@ -293,10 +235,6 @@ export function ConversationView({
293235
id != null ? itemIdToRowIndexRef.current.get(id) : undefined;
294236
listRef.current?.scrollToIndex(rowIdx ?? index);
295237
},
296-
// Search doesn't read scroll/content geometry, so these forward to the real
297-
// handle purely to satisfy the (now-extended) VirtualizedListHandle shape.
298-
getScrollElement: () => listRef.current?.getScrollElement() ?? null,
299-
getContentElement: () => listRef.current?.getContentElement() ?? null,
300238
});
301239

302240
const search = useConversationSearch({
@@ -305,8 +243,10 @@ export function ConversationView({
305243
listRef: searchListRef,
306244
});
307245

308-
const { jumpPickerOpen, keyboardFocusedMessageId, scrollElements } =
309-
viewState;
246+
const [jumpPickerOpen, setJumpPickerOpen] = useState(false);
247+
const [keyboardFocusedMessageId, setKeyboardFocusedMessageId] = useState<
248+
string | null
249+
>(null);
310250

311251
const userMessages = useMemo(() => {
312252
const result: Array<{ id: string; index: number; content: string }> = [];
@@ -352,22 +292,15 @@ export function ConversationView({
352292
if (!nextMessage) return;
353293

354294
useSettingsStore.getState().markHintLearned(PROMPT_RECALL_HINT_KEY);
355-
dispatchViewState({
356-
type: "set-keyboard-focused-message",
357-
value: nextMessage.id,
358-
});
295+
setKeyboardFocusedMessageId(nextMessage.id);
359296
scrollToUserMessage(nextMessage.id, nextMessage.index);
360297
},
361298
[keyboardFocusedMessageId, userMessages, scrollToUserMessage],
362299
);
363300

364301
useHotkeys(
365302
SHORTCUTS.MESSAGE_JUMP,
366-
() =>
367-
dispatchViewState({
368-
type: "set-jump-picker-open",
369-
value: !jumpPickerOpen,
370-
}),
303+
() => setJumpPickerOpen((prev) => !prev),
371304
THREAD_HOTKEY_OPTIONS,
372305
);
373306

@@ -384,73 +317,34 @@ export function ConversationView({
384317
);
385318

386319
const clearKeyboardFocus = useCallback(() => {
387-
dispatchViewState({ type: "set-keyboard-focused-message", value: null });
320+
setKeyboardFocusedMessageId(null);
388321
}, []);
389322

390323
const handleJumpToMessage = useCallback(
391324
(id: string) => {
392325
const message = userMessages.find((entry) => entry.id === id);
393326
if (!message) return;
394-
dispatchViewState({ type: "set-keyboard-focused-message", value: id });
327+
setKeyboardFocusedMessageId(id);
395328
scrollToUserMessage(id, message.index);
396329
},
397330
[userMessages, scrollToUserMessage],
398331
);
399332

400-
// The scrollbar marker rail needs the VirtualizedList's scroll + content
401-
// elements. Resolve them from the list ref callback so the rail renders as
402-
// soon as the imperative handle is attached, without mount-time state setup.
403-
const setListRef = useCallback((handle: VirtualizedListHandle | null) => {
404-
listRef.current = handle;
405-
dispatchViewState({
406-
type: "set-scroll-elements",
407-
value: {
408-
scrollEl: handle?.getScrollElement() ?? null,
409-
contentEl: handle?.getContentElement() ?? null,
410-
},
411-
});
412-
}, []);
413-
414-
const railUserMessages = useMemo(
415-
() =>
416-
userMessages.map((m) => ({
417-
id: m.id,
418-
content: m.content,
419-
index: m.index,
420-
})),
421-
[userMessages],
422-
);
423-
const railMarkers = useMessageRailMarkers({
424-
contentEl: scrollElements.contentEl,
425-
scrollEl: scrollElements.scrollEl,
426-
userMessages: railUserMessages,
427-
onJump: (id) => {
428-
const message = userMessages.find((entry) => entry.id === id);
429-
if (!message) return;
430-
dispatchViewState({ type: "set-keyboard-focused-message", value: id });
431-
scrollToUserMessage(id, message.index);
432-
},
433-
activeId: keyboardFocusedMessageId,
434-
});
435-
436333
const handleScrollStateChange = useCallback((isAtBottom: boolean) => {
437334
isAtBottomRef.current = isAtBottom;
438-
dispatchViewState({
439-
type: "set-show-scroll-button",
440-
value: !isAtBottom,
441-
});
335+
setShowScrollButton(!isAtBottom);
442336
}, []);
443337

444338
const scrollToBottom = useCallback(() => {
445339
listRef.current?.scrollToBottom();
446-
dispatchViewState({ type: "set-show-scroll-button", value: false });
340+
setShowScrollButton(false);
447341
}, []);
448342

449343
useEffect(() => {
450344
const handleVisibilityChange = () => {
451345
if (!document.hidden && isAtBottomRef.current) {
452346
listRef.current?.scrollToBottom();
453-
dispatchViewState({ type: "set-show-scroll-button", value: false });
347+
setShowScrollButton(false);
454348
}
455349
};
456350

@@ -607,16 +501,14 @@ export function ConversationView({
607501

608502
<MessageJumpPicker
609503
open={jumpPickerOpen}
610-
onOpenChange={(open) =>
611-
dispatchViewState({ type: "set-jump-picker-open", value: open })
612-
}
504+
onOpenChange={setJumpPickerOpen}
613505
items={items}
614506
onJumpToMessage={handleJumpToMessage}
615507
/>
616508

617509
<SessionTaskIdProvider taskId={taskId}>
618510
<VirtualizedList<ThreadRow>
619-
ref={setListRef}
511+
ref={listRef}
620512
items={threadRows}
621513
getItemKey={getRowKey}
622514
renderItem={renderRow}
@@ -629,8 +521,7 @@ export function ConversationView({
629521
scrollX={scrollX}
630522
/>
631523
</SessionTaskIdProvider>
632-
<MessageScrollbarRail markers={railMarkers} />
633-
{viewState.showScrollButton && (
524+
{showScrollButton && (
634525
<Box className="absolute right-6 bottom-4 z-10">
635526
<Tooltip>
636527
<TooltipTrigger

‎packages/ui/src/features/sessions/components/VirtualizedList.tsx‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ interface VirtualizedListProps<T> {
3535
export interface VirtualizedListHandle {
3636
scrollToBottom: () => void;
3737
scrollToIndex: (index: number) => void;
38-
/** The scrolling element (the `overflow-y-auto` viewport). Exposed so a
39-
* scrollbar marker rail can read `scrollTop`/`scrollHeight` and refresh on
40-
* scroll. `null` until the list mounts. */
41-
getScrollElement: () => HTMLDivElement | null;
42-
/** The inner content element whose height == the virtual total size. Rows
43-
* (and their `data-conversation-item-id` stamps) live inside this, so a
44-
* marker rail measures row offsets against it. `null` until the list mounts. */
45-
getContentElement: () => HTMLDivElement | null;
4638
}
4739

4840
const AT_BOTTOM_THRESHOLD = 50;
@@ -71,7 +63,6 @@ function VirtualizedListInner<T>(
7163
ref: React.ForwardedRef<VirtualizedListHandle>,
7264
) {
7365
const parentRef = useRef<HTMLDivElement>(null);
74-
const contentRef = useRef<HTMLDivElement>(null);
7566
const footerRef = useRef<HTMLDivElement>(null);
7667
const initializedRef = useRef(false);
7768
const isAtBottomRef = useRef(true);
@@ -165,8 +156,6 @@ function VirtualizedListInner<T>(
165156
isAtBottomRef.current = false;
166157
virtualizer.scrollToIndex(index, { align: "center" });
167158
},
168-
getScrollElement: () => parentRef.current,
169-
getContentElement: () => contentRef.current,
170159
}),
171160
[virtualizer, settleAtEnd],
172161
);
@@ -269,7 +258,6 @@ function VirtualizedListInner<T>(
269258
style={{ scrollbarGutter: "stable" }}
270259
>
271260
<div
272-
ref={contentRef}
273261
style={{
274262
height: totalSize,
275263
position: "relative",

0 commit comments

Comments
 (0)