Skip to content

Commit 62ecc74

Browse files
committed
chore(file-viewer): update scroll-anchor tsdoc, remove test separators, add hydrate linger tests
1 parent 3f6c813 commit 62ecc74

3 files changed

Lines changed: 86 additions & 23 deletions

File tree

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

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,11 +176,6 @@ describe('reduceFilePreviewSessions', () => {
176176
})
177177

178178
it('lingers on the completed session when it is the only one (no successor)', () => {
179-
// Completing the only session must NOT drop activeSessionId to null.
180-
// The linger keeps the completed session active so downstream consumers
181-
// continue to receive previewText and the file viewer stays mounted,
182-
// preserving the user's scroll position until a new tool call arrives
183-
// or the session store is reset.
184179
const onlyStreaming = reduceFilePreviewSessions(INITIAL_FILE_PREVIEW_SESSIONS_STATE, {
185180
type: 'upsert',
186181
session: createSession({
@@ -247,9 +242,6 @@ describe('reduceFilePreviewSessions', () => {
247242
})
248243

249244
it('holds the linger when an empty pending session arrives (no content yet)', () => {
250-
// Between tool calls, a new empty/pending session may arrive before its
251-
// first content chunk. The active session must not switch to it so the
252-
// file viewer stays mounted and scroll position is preserved.
253245
const lingered = reduceFilePreviewSessions(INITIAL_FILE_PREVIEW_SESSIONS_STATE, {
254246
type: 'upsert',
255247
session: createSession({
@@ -271,7 +263,6 @@ describe('reduceFilePreviewSessions', () => {
271263
}),
272264
})
273265

274-
// New session arrives but with no renderable content (pending, empty).
275266
const afterEmptyUpsert = reduceFilePreviewSessions(afterComplete, {
276267
type: 'upsert',
277268
session: createSession({
@@ -285,7 +276,6 @@ describe('reduceFilePreviewSessions', () => {
285276

286277
expect(afterEmptyUpsert.activeSessionId).toBe('preview-1')
287278

288-
// Once the first content chunk arrives, active switches.
289279
const afterContent = reduceFilePreviewSessions(afterEmptyUpsert, {
290280
type: 'upsert',
291281
session: createSession({
@@ -467,6 +457,88 @@ describe('reduceFilePreviewSessions', () => {
467457
expect(hydrated.sessions['preview-2']?.previewText).toBe('new')
468458
})
469459

460+
it('hydrate preserves linger when no non-complete session exists in incoming batch', () => {
461+
const lingered = reduceFilePreviewSessions(
462+
reduceFilePreviewSessions(INITIAL_FILE_PREVIEW_SESSIONS_STATE, {
463+
type: 'upsert',
464+
session: createSession({
465+
id: 'preview-1',
466+
toolCallId: 'preview-1',
467+
previewVersion: 2,
468+
previewText: 'final',
469+
}),
470+
}),
471+
{
472+
type: 'complete',
473+
session: createSession({
474+
id: 'preview-1',
475+
toolCallId: 'preview-1',
476+
status: 'complete',
477+
previewVersion: 3,
478+
completedAt: '2026-04-10T00:00:02.000Z',
479+
previewText: 'final',
480+
}),
481+
}
482+
)
483+
484+
// Hydrate with the same completed session — no non-complete successor.
485+
const afterHydrate = reduceFilePreviewSessions(lingered, {
486+
type: 'hydrate',
487+
sessions: [
488+
createSession({
489+
id: 'preview-1',
490+
toolCallId: 'preview-1',
491+
status: 'complete',
492+
previewVersion: 3,
493+
previewText: 'final',
494+
completedAt: '2026-04-10T00:00:02.000Z',
495+
}),
496+
],
497+
})
498+
499+
expect(afterHydrate.activeSessionId).toBe('preview-1')
500+
})
501+
502+
it('hydrate releases linger when a non-complete session is present in the incoming batch', () => {
503+
const lingered = reduceFilePreviewSessions(
504+
reduceFilePreviewSessions(INITIAL_FILE_PREVIEW_SESSIONS_STATE, {
505+
type: 'upsert',
506+
session: createSession({
507+
id: 'preview-1',
508+
toolCallId: 'preview-1',
509+
previewVersion: 2,
510+
previewText: 'final',
511+
}),
512+
}),
513+
{
514+
type: 'complete',
515+
session: createSession({
516+
id: 'preview-1',
517+
toolCallId: 'preview-1',
518+
status: 'complete',
519+
previewVersion: 3,
520+
completedAt: '2026-04-10T00:00:02.000Z',
521+
previewText: 'final',
522+
}),
523+
}
524+
)
525+
526+
const afterHydrate = reduceFilePreviewSessions(lingered, {
527+
type: 'hydrate',
528+
sessions: [
529+
createSession({
530+
id: 'preview-2',
531+
toolCallId: 'preview-2',
532+
status: 'streaming',
533+
previewVersion: 1,
534+
previewText: 'new content',
535+
}),
536+
],
537+
})
538+
539+
expect(afterHydrate.activeSessionId).toBe('preview-2')
540+
})
541+
470542
it('complete for a non-active session updates the session but keeps activeSessionId', () => {
471543
let state = reduceFilePreviewSessions(INITIAL_FILE_PREVIEW_SESSIONS_STATE, {
472544
type: 'upsert',

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

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import { describe, expect, it } from 'vitest'
1010
import { computeSpacerShortage } from '@/hooks/use-scroll-anchor'
1111

1212
describe('computeSpacerShortage', () => {
13-
// ── no shortage needed ────────────────────────────────────────────────────
14-
1513
it('returns 0 when content is exactly tall enough', () => {
1614
// user at 500, viewport 600 → needs 1100; content is exactly 1100
1715
expect(computeSpacerShortage(500, 600, 1100, 0)).toBe(0)
@@ -31,8 +29,6 @@ describe('computeSpacerShortage', () => {
3129
expect(computeSpacerShortage(0, 600, 1000, 0)).toBe(0)
3230
})
3331

34-
// ── shortage cases ───────────────────────────────────────────────────────
35-
3632
it('returns positive shortage when content shrank to almost nothing', () => {
3733
// user at 500, viewport 600 → needs 1100; content shrank to 100
3834
expect(computeSpacerShortage(500, 600, 100, 0)).toBe(1000)
@@ -47,8 +43,6 @@ describe('computeSpacerShortage', () => {
4743
expect(computeSpacerShortage(500, 600, 1099, 0)).toBe(1)
4844
})
4945

50-
// ── spacer already inflated from a previous update ───────────────────────
51-
5246
it('subtracts existing spacer height before recomputing shortage', () => {
5347
// spacer was 900 from last update; content grew to 1000 natural height
5448
// scrollHeight = 1000 + 900 = 1900; needed = 500 + 600 = 1100
@@ -76,8 +70,6 @@ describe('computeSpacerShortage', () => {
7670
expect(computeSpacerShortage(500, 600, 2000, 1200)).toBe(300)
7771
})
7872

79-
// ── user scrolled deep into a long document ──────────────────────────────
80-
8173
it('handles large scroll positions correctly', () => {
8274
// user at 5000, viewport 600 → needs 5600; content shrank to 200
8375
expect(computeSpacerShortage(5000, 600, 200, 0)).toBe(5400)
@@ -89,8 +81,6 @@ describe('computeSpacerShortage', () => {
8981
expect(computeSpacerShortage(1400, 600, 100, 0)).toBe(1900)
9082
})
9183

92-
// ── edge: zero-height content (first chunk of a fresh write) ─────────────
93-
9484
it('handles zero-height content', () => {
9585
// first streaming chunk is empty; user was at 500
9686
expect(computeSpacerShortage(500, 600, 0, 0)).toBe(1100)

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ export function computeSpacerShortage(
2121
* Manages scroll for a streaming file-preview container.
2222
*
2323
* 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.
24+
* keeps it pinned). Scrolled-up: position is locked via a spacer element that
25+
* inflates `scrollHeight` to prevent the browser from clamping `scrollTop` when
26+
* replace-mode streaming temporarily produces a shorter chunk. Scrolled back to
27+
* the bottom: auto-follow re-engages.
2728
*
2829
* @param isStreaming - whether the container is currently receiving streaming content
2930
* @param content - drives spacer recalculation; pass the current text value

0 commit comments

Comments
 (0)