Skip to content

Commit 77f6364

Browse files
committed
chore(file-viewer): trim verbose comments to match codebase style
1 parent f021927 commit 77f6364

2 files changed

Lines changed: 17 additions & 86 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/hooks/use-file-preview-sessions.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,6 @@ export function reduceFilePreviewSessions(
9393
const successor = pickActiveSessionId(nextSessions, state.activeSessionId)
9494
return {
9595
sessions: nextSessions,
96-
// Preserve linger: if the active session is a completed (lingered) one
97-
// and no non-complete successor exists, keep it rather than going null.
9896
activeSessionId: successor ?? state.activeSessionId,
9997
}
10098
}
@@ -114,10 +112,7 @@ export function reduceFilePreviewSessions(
114112
const successor = pickActiveSessionId(nextSessions, state.activeSessionId)
115113
nextActiveSessionId = successor ?? state.activeSessionId
116114
} else {
117-
// Don't steal active from a session that already has renderable content
118-
// just because a new empty/pending session arrived. Wait until the new
119-
// session has actual content before switching, so the file viewer stays
120-
// mounted and the user's scroll position is preserved between tool calls.
115+
// Don't switch to a new session until it has renderable content — keeps the viewer mounted.
121116
const currentActive = state.activeSessionId ? nextSessions[state.activeSessionId] : null
122117
const currentHasContent = currentActive
123118
? hasRenderableFilePreviewContent(currentActive)
@@ -147,11 +142,8 @@ export function reduceFilePreviewSessions(
147142
const successor = pickActiveSessionId(nextSessions, null)
148143
return {
149144
sessions: nextSessions,
150-
// Linger on the completed session when no non-complete successor exists.
151-
// This prevents a streamingContent → undefined flicker that would collapse
152-
// the file viewer to shorter fetched content, clip scrollTop, and jump the
153-
// user's reading position. The linger ends when a new non-complete session
154-
// upserts (switching activeSessionId) or reset fires.
145+
// Linger on this session until a successor upserts. Without it, streamingContent
146+
// becomes undefined between tool calls, collapsing the viewer and clipping scrollTop.
155147
activeSessionId: successor ?? action.session.id,
156148
}
157149
}

apps/sim/hooks/use-scroll-anchor.ts

Lines changed: 14 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,8 @@ import { useCallback, useLayoutEffect, useRef } from 'react'
33
const NEAR_BOTTOM_THRESHOLD = 30
44

55
/**
6-
* Computes how much extra height the scroll-anchor spacer needs to maintain
7-
* the user's intended scroll position when replace-mode streaming temporarily
8-
* produces content shorter than that position.
9-
*
10-
* @param targetScrollTop - the scroll position the user intends to hold
11-
* @param clientHeight - visible height of the scroll container
12-
* @param scrollHeight - total scrollable height (including current spacer)
13-
* @param prevSpacerHeight - current spacer height (subtracted to get natural content height)
14-
* @returns the new `minHeight` value to apply to the spacer element, or 0 if none needed
6+
* Returns the `minHeight` the spacer needs so `scrollTop` can safely reach
7+
* `targetScrollTop` when replace-mode streaming produces temporarily shorter content.
158
*/
169
export function computeSpacerShortage(
1710
targetScrollTop: number,
@@ -25,35 +18,22 @@ export function computeSpacerShortage(
2518
}
2619

2720
/**
28-
* Manages scroll for a streaming content container.
29-
*
30-
* Two modes based on whether the user has ever manually scrolled:
31-
*
32-
* **Never scrolled** — auto-follows new content to the bottom while
33-
* streaming (MutationObserver keeps it pinned). The user can scroll up to
34-
* detach; scrolling back to the bottom re-engages.
21+
* Manages scroll for a streaming file-preview container.
3522
*
36-
* **Has scrolled** — position is locked. The hook injects a spacer element
37-
* at the end of the container's content that inflates `scrollHeight` to at
38-
* least `intendedScrollTop + clientHeight` on every content update. This
39-
* prevents the browser from clamping `scrollTop` when replace-mode streaming
40-
* temporarily produces a chunk shorter than the previous content (which would
41-
* otherwise jump the viewport to the top before the content regrows).
42-
*
43-
* The "has scrolled" flag resets on unmount so each new chat / file gets a
44-
* clean slate (parent remounts on key change).
23+
* Never-scrolled: auto-follows new content to the bottom (MutationObserver
24+
* keeps it pinned). Has-scrolled: position is locked via a spacer element
25+
* that inflates `scrollHeight` to prevent the browser from clamping `scrollTop`
26+
* when replace-mode streaming temporarily produces a shorter chunk.
4527
*
4628
* @param isStreaming - whether the container is currently receiving streaming content
47-
* @param content - the current text content; drives the spacer recalculation
29+
* @param content - drives spacer recalculation; pass the current text value
4830
*/
4931
export function useScrollAnchor(isStreaming: boolean, content?: string) {
5032
const containerRef = useRef<HTMLDivElement | null>(null)
5133
const spacerRef = useRef<HTMLDivElement | null>(null)
5234
const hasUserScrolledRef = useRef(false)
5335
const stickyRef = useRef(false)
54-
55-
// The scroll position the user most recently settled on — updated only from
56-
// genuine user scroll events, never from programmatic ones.
36+
// Tracks the user's last intentional position; updated only on genuine user events, never programmatic ones.
5737
const intendedScrollTopRef = useRef(0)
5838

5939
const scrollToBottom = useCallback(() => {
@@ -62,11 +42,9 @@ export function useScrollAnchor(isStreaming: boolean, content?: string) {
6242
el.scrollTop = el.scrollHeight
6343
}, [])
6444

65-
// ── event listeners ─────────────────────────────────────────────────────
66-
6745
const onWheel = useCallback((e: WheelEvent) => {
6846
if (e.deltaY >= 0 || hasUserScrolledRef.current) return
69-
// User scrolled up before any scroll event fireddetach immediately.
47+
// Upward wheel before any scroll event firesmark detached immediately.
7048
hasUserScrolledRef.current = true
7149
stickyRef.current = false
7250
const el = containerRef.current
@@ -78,25 +56,20 @@ export function useScrollAnchor(isStreaming: boolean, content?: string) {
7856
if (!el) return
7957

8058
if (hasUserScrolledRef.current) {
81-
// Track their position so we can restore it after a content-shrink event.
8259
intendedScrollTopRef.current = el.scrollTop
8360
return
8461
}
8562

8663
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight
8764
if (distanceFromBottom > NEAR_BOTTOM_THRESHOLD) {
88-
// User scrolled away from the bottom.
8965
hasUserScrolledRef.current = true
9066
stickyRef.current = false
9167
intendedScrollTopRef.current = el.scrollTop
9268
} else {
93-
// Re-engaged (scrolled back to bottom).
9469
stickyRef.current = true
9570
}
9671
}, [])
9772

98-
// ── container ref callback ───────────────────────────────────────────────
99-
10073
const callbackRef = useCallback(
10174
(el: HTMLDivElement | null) => {
10275
const prev = containerRef.current
@@ -113,26 +86,16 @@ export function useScrollAnchor(isStreaming: boolean, content?: string) {
11386
[onScroll, onWheel]
11487
)
11588

116-
// ── stream-start: decide initial pin state ───────────────────────────────
117-
11889
useLayoutEffect(() => {
11990
if (!isStreaming) return
12091
const el = containerRef.current
12192
if (!el) return
122-
123-
if (hasUserScrolledRef.current) {
124-
// User has already scrolled — never override their position.
125-
return
126-
}
127-
128-
// Fresh stream or user is at the bottom — engage sticky follow.
93+
if (hasUserScrolledRef.current) return
12994
const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight
13095
stickyRef.current = distanceFromBottom <= NEAR_BOTTOM_THRESHOLD
13196
if (stickyRef.current) scrollToBottom()
13297
}, [isStreaming, scrollToBottom])
13398

134-
// ── sticky follow: MutationObserver while streaming ──────────────────────
135-
13699
useLayoutEffect(() => {
137100
if (!isStreaming) return
138101
const el = containerRef.current
@@ -157,39 +120,20 @@ export function useScrollAnchor(isStreaming: boolean, content?: string) {
157120
}
158121
}, [isStreaming, scrollToBottom])
159122

160-
// ── scroll-clip prevention via spacer ───────────────────────────────────
161-
//
162-
// On every content update, if the user has scrolled:
163-
// 1. Compute naturalScrollHeight (container height minus spacer contribution).
164-
// 2. Compute the minimum scrollHeight needed to keep intendedScrollTop valid.
165-
// 3. Set spacer.minHeight to fill any gap — this inflates scrollHeight before
166-
// we read scrollTop, so the browser never clamps it to 0.
167-
// 4. Restore scrollTop if it was already clipped (e.g. by prior content).
168-
//
169-
// When the user has NOT scrolled: clear the spacer so it doesn't interfere
170-
// with auto-scroll bottom detection.
171-
172123
useLayoutEffect(() => {
173124
const el = containerRef.current
174125
const spacer = spacerRef.current
175126
if (!el) return
176127

177-
// Clear the spacer when the user hasn't scrolled (auto-follow mode) or when
178-
// streaming has ended (content is stable; no more clip-prevention needed).
179128
if (!hasUserScrolledRef.current || !isStreaming) {
180129
if (spacer) spacer.style.minHeight = '0'
181130
return
182131
}
183132

184-
// Capture the target BEFORE any layout read. Reading layout properties
185-
// (clientHeight, scrollHeight, offsetHeight) forces a browser reflow. If
186-
// scrollHeight is already smaller than scrollTop + clientHeight, the browser
187-
// clamps scrollTop to 0 during that reflow and dispatches a 'scroll' event
188-
// synchronously. Our onScroll handler would then overwrite intendedScrollTopRef
189-
// with 0, corrupting the restore. Saving to a local variable here avoids that.
133+
// Capture before any layout read: reading scrollHeight forces a reflow which can
134+
// synchronously fire 'scroll' and overwrite intendedScrollTopRef with the clamped value.
190135
const targetScrollTop = intendedScrollTopRef.current
191136

192-
// Read spacer and scroll heights BEFORE mutating the spacer.
193137
const prevSpacerHeight = spacer ? spacer.offsetHeight : 0
194138
const shortage = computeSpacerShortage(
195139
targetScrollTop,
@@ -198,13 +142,8 @@ export function useScrollAnchor(isStreaming: boolean, content?: string) {
198142
prevSpacerHeight
199143
)
200144

201-
// Inflate spacer so scrollHeight >= needed, preventing scrollTop clamping.
202145
if (spacer) spacer.style.minHeight = `${shortage}px`
203-
204-
// Restore scroll position (now valid because spacer ensures enough height).
205-
if (el.scrollTop < targetScrollTop) {
206-
el.scrollTop = targetScrollTop
207-
}
146+
if (el.scrollTop < targetScrollTop) el.scrollTop = targetScrollTop
208147
}, [content, isStreaming])
209148

210149
return {

0 commit comments

Comments
 (0)