debug: add temporary console.log statements for virtualization tracking

- Add [VIRTUALIZATION] prefixed logs to track viewport changes
- Log scroll events, visible ranges, and performance metrics
- Track auto-scroll decisions and user scroll detection
- Monitor state cache hits/misses and evictions
- Include timestamps and relevant data for debugging

These logs help verify the virtualization behavior during testing.
This commit is contained in:
hannesrudolph 2025-08-01 15:59:04 -06:00
parent 227cd9c436
commit 55097ba8a3
4 changed files with 196 additions and 19 deletions

View file

@ -545,10 +545,18 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
isStreaming,
isHidden,
onPerformanceIssue: (metric, value) => {
console.warn(`ChatView performance issue: ${metric} = ${value}`)
console.warn(`[VIRTUALIZATION] ChatView performance issue: ${metric} = ${value}`)
},
})
console.log("[VIRTUALIZATION] Virtualization hook initialized:", {
messagesCount: modifiedMessages.length,
isStreaming,
isHidden,
viewportConfig,
timestamp: new Date().toISOString(),
})
// Sync expanded rows with state manager
useEffect(() => {
if (stateManager) {
@ -559,6 +567,12 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
}
})
setExpandedRows(newExpandedRows)
console.log("[VIRTUALIZATION] Synced expanded rows with state manager:", {
expandedCount: Object.keys(newExpandedRows).length,
visibleRange,
timestamp: new Date().toISOString(),
})
}
}, [modifiedMessages, stateManager, visibleRange])
@ -1404,9 +1418,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// scrolling - use the optimized versions
const scrollToBottomSmooth = useMemo(
() =>
debounce(() => optimizedScrollToBottom("smooth"), 10, {
immediate: true,
}),
debounce(
() => {
console.log("[VIRTUALIZATION] Smooth scroll to bottom triggered")
optimizedScrollToBottom("smooth")
},
10,
{
immediate: true,
},
),
[optimizedScrollToBottom],
)
@ -1419,6 +1440,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
}, [scrollToBottomSmooth])
const scrollToBottomAuto = useCallback(() => {
console.log("[VIRTUALIZATION] Auto scroll to bottom triggered")
optimizedScrollToBottom("auto")
}, [optimizedScrollToBottom])
@ -1580,6 +1602,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
const itemContent = useCallback(
(index: number, messageOrGroup: ClineMessage | ClineMessage[]) => {
console.log("[VIRTUALIZATION] Rendering item at index:", {
index,
isGroup: Array.isArray(messageOrGroup),
messageTs: Array.isArray(messageOrGroup) ? messageOrGroup[0]?.ts : messageOrGroup.ts,
timestamp: new Date().toISOString(),
})
// browser session group
if (Array.isArray(messageOrGroup)) {
return (
@ -1959,12 +1988,21 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
itemContent={itemContent}
onScroll={(e) => {
const target = e.currentTarget as HTMLElement
handleVirtuosoScroll(target.scrollTop)
handleScrollStateChange({
const scrollState = {
scrollTop: target.scrollTop,
scrollHeight: target.scrollHeight,
viewportHeight: target.clientHeight,
}
console.log("[VIRTUALIZATION] Virtuoso scroll event:", {
...scrollState,
distanceFromBottom:
scrollState.scrollHeight - scrollState.scrollTop - scrollState.viewportHeight,
timestamp: new Date().toISOString(),
})
handleVirtuosoScroll(target.scrollTop)
handleScrollStateChange(scrollState)
}}
rangeChanged={handleRangeChange}
atBottomStateChange={(atBottom) => {

View file

@ -104,18 +104,29 @@ export function useOptimizedVirtualization({
const devicePerf = detectDevicePerformance()
const hasExpanded = stateManager.hasExpandedMessages()
console.log("[VIRTUALIZATION] Viewport config calculation:", {
devicePerf,
hasExpanded,
isStreaming,
timestamp: new Date().toISOString(),
})
// Streaming takes priority
if (isStreaming) {
console.log("[VIRTUALIZATION] Using streaming viewport config:", config.viewport.streaming)
return config.viewport.streaming
}
// Expanded messages need more buffer
if (hasExpanded) {
console.log("[VIRTUALIZATION] Using expanded viewport config:", config.viewport.expanded)
return config.viewport.expanded
}
// Use device-specific config
return getViewportConfigForDevice(devicePerf)
const deviceConfig = getViewportConfigForDevice(devicePerf)
console.log("[VIRTUALIZATION] Using device-specific viewport config:", deviceConfig)
return deviceConfig
}, [isStreaming, stateManager, config])
// Create optimized message groups
@ -132,6 +143,14 @@ export function useOptimizedVirtualization({
// Use stored scroll state
const { scrollHeight, viewportHeight } = scrollState
console.log("[VIRTUALIZATION] Scroll event:", {
scrollTop,
scrollHeight,
viewportHeight,
distanceFromBottom: scrollHeight - scrollTop - viewportHeight,
timestamp: new Date().toISOString(),
})
scrollManager.handleScroll(scrollTop, scrollHeight, viewportHeight)
// Update performance metrics
@ -141,6 +160,12 @@ export function useOptimizedVirtualization({
const atBottom = scrollManager.isAtBottom(scrollTop, scrollHeight, viewportHeight)
setIsAtBottom(atBottom)
setShowScrollToBottom(!atBottom && scrollManager.getState().isUserScrolling)
console.log("[VIRTUALIZATION] Scroll state updated:", {
isAtBottom: atBottom,
showScrollToBottom: !atBottom && scrollManager.getState().isUserScrolling,
userScrolling: scrollManager.getState().isUserScrolling,
})
},
[scrollState, scrollManager, performanceMonitor],
)
@ -148,29 +173,47 @@ export function useOptimizedVirtualization({
// Handle visible range changes
const handleRangeChange = useCallback(
(range: { startIndex: number; endIndex: number }) => {
console.log("[VIRTUALIZATION] Visible range changed:", {
startIndex: range.startIndex,
endIndex: range.endIndex,
totalGroups: messageGroups.length,
timestamp: new Date().toISOString(),
})
setVisibleRange(range)
// Update performance metrics
const messageIndices = getVisibleMessageIndices(messageGroups, range)
performanceMonitor.updateMessageCounts(
messages.length,
messageIndices.endIndex - messageIndices.startIndex + 1,
)
const visibleMessageCount = messageIndices.endIndex - messageIndices.startIndex + 1
performanceMonitor.updateMessageCounts(messages.length, visibleMessageCount)
console.log("[VIRTUALIZATION] Performance metrics updated:", {
totalMessages: messages.length,
visibleMessages: visibleMessageCount,
messageIndices,
})
// Pin important messages in visible range
const visibleGroups = messageGroups.slice(range.startIndex, range.endIndex + 1)
let pinnedCount = 0
visibleGroups.forEach((group) => {
group.messages.forEach((msg) => {
// Pin error messages and active tools
if (msg.ask === "api_req_failed" || msg.say === "error" || (msg.ask === "tool" && !msg.partial)) {
stateManager.pinMessage(msg.ts)
pinnedCount++
}
})
})
if (pinnedCount > 0) {
console.log("[VIRTUALIZATION] Pinned messages in visible range:", pinnedCount)
}
// Cleanup old states periodically
if (Math.random() < 0.1) {
// 10% chance on each range change
console.log("[VIRTUALIZATION] Running state cleanup")
stateManager.cleanup()
}
},
@ -218,11 +261,15 @@ export function useOptimizedVirtualization({
performanceMonitor.updateDOMNodeCount()
// Log metrics in development
if (process.env.NODE_ENV === "development") {
const report = performanceMonitor.getReport()
if (report.issues.length > 0) {
console.warn("Performance issues detected:", report.issues)
}
const report = performanceMonitor.getReport()
console.log("[VIRTUALIZATION] Performance report:", {
metrics: report.metrics,
issues: report.issues,
timestamp: new Date().toISOString(),
})
if (report.issues.length > 0) {
console.warn("[VIRTUALIZATION] Performance issues detected:", report.issues)
}
}, 5000)

View file

@ -36,16 +36,34 @@ export class AutoScrollManager {
const isScrollingUp = deltaScroll < -5 // Small threshold to avoid noise
const significantScroll = Math.abs(deltaScroll) > 10
console.log("[VIRTUALIZATION] AutoScrollManager.handleScroll:", {
scrollTop,
scrollHeight,
clientHeight,
deltaScroll,
deltaTime,
distanceFromBottom,
scrollVelocity: this.scrollVelocity,
isScrollingUp,
significantScroll,
timestamp: new Date().toISOString(),
})
if (isScrollingUp && distanceFromBottom > this.atBottomThreshold) {
console.log("[VIRTUALIZATION] User scrolling detected: scrolling up")
this.isUserScrolling = true
this.isScrolling = true
} else if (significantScroll && !this.isNearBottom(scrollTop, scrollHeight, clientHeight)) {
console.log("[VIRTUALIZATION] User scrolling detected: significant scroll")
this.isUserScrolling = true
this.isScrolling = true
}
// Reset user scrolling flag if they scroll to bottom
if (distanceFromBottom <= this.atBottomThreshold) {
if (this.isUserScrolling) {
console.log("[VIRTUALIZATION] User scrolling reset: reached bottom")
}
this.isUserScrolling = false
}
@ -61,6 +79,7 @@ export class AutoScrollManager {
// Set timeout to detect end of scrolling
this.scrollTimeout = setTimeout(() => {
console.log("[VIRTUALIZATION] Scrolling ended")
this.isScrolling = false
this.scrollVelocity = 0
this.scrollTimeout = null
@ -75,7 +94,17 @@ export class AutoScrollManager {
// 1. User is manually scrolling
// 2. There are expanded messages (user might be reading)
// 3. Currently in a scroll animation
return !this.isUserScrolling && !hasExpandedMessages && !this.isScrolling
const shouldScroll = !this.isUserScrolling && !hasExpandedMessages && !this.isScrolling
console.log("[VIRTUALIZATION] AutoScrollManager.shouldAutoScroll:", {
shouldScroll,
isUserScrolling: this.isUserScrolling,
hasExpandedMessages,
isScrolling: this.isScrolling,
timestamp: new Date().toISOString(),
})
return shouldScroll
}
/**
@ -98,6 +127,7 @@ export class AutoScrollManager {
* Reset user scrolling flag
*/
resetUserScrolling(): void {
console.log("[VIRTUALIZATION] User scrolling reset manually")
this.isUserScrolling = false
}
@ -105,6 +135,7 @@ export class AutoScrollManager {
* Force user scrolling state (e.g., when user expands a message)
*/
forceUserScrolling(): void {
console.log("[VIRTUALIZATION] User scrolling forced")
this.isUserScrolling = true
}
@ -127,6 +158,16 @@ export class AutoScrollManager {
*/
getScrollBehavior(currentTop: number, targetTop: number, maxSmoothDistance: number = 5000): ScrollBehavior {
const distance = Math.abs(targetTop - currentTop)
const behavior = distance > maxSmoothDistance ? "auto" : "smooth"
console.log("[VIRTUALIZATION] Scroll behavior calculated:", {
currentTop,
targetTop,
distance,
maxSmoothDistance,
behavior,
timestamp: new Date().toISOString(),
})
// Use instant scroll for large jumps to avoid janky animation
if (distance > maxSmoothDistance) {

View file

@ -25,11 +25,17 @@ export class MessageStateManager {
ttl: ttl,
updateAgeOnGet: true,
updateAgeOnHas: true,
dispose: (value, _key) => {
dispose: (value, key) => {
// Update expanded count when items are evicted
if (value.isExpanded) {
this.expandedCount = Math.max(0, this.expandedCount - 1)
}
console.log("[VIRTUALIZATION] MessageStateManager state evicted:", {
messageTs: key,
wasExpanded: value.isExpanded,
expandedCount: this.expandedCount,
timestamp: new Date().toISOString(),
})
},
})
this.pinnedMessages = new Set<number>()
@ -39,7 +45,20 @@ export class MessageStateManager {
* Get the state of a message
*/
getState(messageTs: number): MessageState | undefined {
return this.states.get(messageTs)
const state = this.states.get(messageTs)
if (state) {
console.log("[VIRTUALIZATION] MessageStateManager cache hit:", {
messageTs,
state,
timestamp: new Date().toISOString(),
})
} else {
console.log("[VIRTUALIZATION] MessageStateManager cache miss:", {
messageTs,
timestamp: new Date().toISOString(),
})
}
return state
}
/**
@ -70,6 +89,15 @@ export class MessageStateManager {
this.expandedCount = Math.max(0, this.expandedCount - 1)
}
console.log("[VIRTUALIZATION] MessageStateManager.setState:", {
messageTs,
previousState: existing,
newState,
expandedCount: this.expandedCount,
cacheSize: this.states.size,
timestamp: new Date().toISOString(),
})
this.states.set(messageTs, newState)
}
@ -172,6 +200,9 @@ export class MessageStateManager {
}
})
const previousSize = this.states.size
const previousExpandedCount = this.expandedCount
// Clear all states
this.states.clear()
this.expandedCount = 0
@ -183,13 +214,33 @@ export class MessageStateManager {
this.expandedCount++
}
})
console.log("[VIRTUALIZATION] MessageStateManager cleared:", {
previousSize,
previousExpandedCount,
newSize: this.states.size,
newExpandedCount: this.expandedCount,
pinnedCount: this.pinnedMessages.size,
timestamp: new Date().toISOString(),
})
}
/**
* Cleanup old states (called automatically by LRU cache)
*/
cleanup(): void {
const beforeSize = this.states.size
this.states.purgeStale()
const afterSize = this.states.size
if (beforeSize !== afterSize) {
console.log("[VIRTUALIZATION] MessageStateManager cleanup:", {
beforeSize,
afterSize,
removed: beforeSize - afterSize,
timestamp: new Date().toISOString(),
})
}
}
/**