From 03fb5c6c675dde01f7a98367515c1ea057bf89c4 Mon Sep 17 00:00:00 2001 From: Luke Parker <10430890+Hona@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:33:32 +1000 Subject: [PATCH] fix(app): prevent clipped virtual timeline rows (#45115) --- .../app/src/session/timeline/virtualizer.tsx | 9 +++++++ .../app/test-browser/solid-virtual.test.ts | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/packages/app/src/session/timeline/virtualizer.tsx b/packages/app/src/session/timeline/virtualizer.tsx index 244136f06a1..d1401f93c95 100644 --- a/packages/app/src/session/timeline/virtualizer.tsx +++ b/packages/app/src/session/timeline/virtualizer.tsx @@ -92,6 +92,15 @@ export function createTimelineVirtualizer(input: Input) { initialOffset: () => (input.pinned() ? Number.MAX_SAFE_INTEGER : 0), initialMeasurementsCache: initialMeasurements, estimateSize: () => fallbackItemSize, + // Do not replace this with TanStack's default measurer: without a ResizeObserver entry, + // it returns the cached height instead of reading the element (TanStack/virtual#1183). + // Restored sessions, deferred tools, and rewrapped content can then keep stale heights; + // our fixed-height, overflow-clipped rows will hide their content. Keep observer entries + // on the cheap precomputed path, but make explicit measurements read the real height. + measureElement: (element, entry) => { + const box = entry?.borderBoxSize[0] + return box ? Math.round(box.blockSize) : element.offsetHeight + }, scrollToFn: (offset, options, instance) => { if (virtualContent) virtualContent.style.height = `${instance.getTotalSize()}px` elementScroll(offset, options, instance) diff --git a/packages/app/test-browser/solid-virtual.test.ts b/packages/app/test-browser/solid-virtual.test.ts index b61b2db5822..ba96d475835 100644 --- a/packages/app/test-browser/solid-virtual.test.ts +++ b/packages/app/test-browser/solid-virtual.test.ts @@ -113,6 +113,32 @@ test("reactive count updates preserve measured row sizes", () => { }) }) +test("explicit measurement refreshes a cached row size with a custom measurer", () => { + const root = document.createElement("div") + const element = document.createElement("div") + element.dataset.index = "0" + Object.defineProperty(element, "offsetHeight", { value: 120 }) + + const virtualizer = new Virtualizer({ + count: 1, + estimateSize: () => 60, + initialRect: { width: 400, height: 200 }, + getScrollElement: () => root, + scrollToFn: () => {}, + observeElementRect: () => {}, + observeElementOffset: () => {}, + measureElement: (node) => node.offsetHeight, + }) + + virtualizer.getTotalSize() + virtualizer.resizeItem(0, 60) + virtualizer._willUpdate() + virtualizer.measureElement(element) + + expect(virtualizer.itemSizeCache.get(0)).toBe(120) + expect(virtualizer.getTotalSize()).toBe(120) +}) + test("initial rect projects rows before a scroll element connects", () => { createRoot((dispose) => { const virtualizer = createVirtualizer({