diff --git a/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts b/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts index 181b65382d..b8b6f31d85 100644 --- a/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts +++ b/apps/desktop/src/components/grid/__tests__/DataGridContextExtractor.spec.ts @@ -608,3 +608,34 @@ describe("DataGrid visible large-value preview lifecycle", () => { expect(visibleHydrationCalls()).toHaveLength(0); }); }); + +describe("DataGrid DOM initial scroll position", () => { + it("keeps the first row visible while the virtual list finishes measuring", async () => { + const { host, replaceResult } = mountGrid(hydratedResult(1, "value")); + await settle(); + + const scroller = host.querySelector(".data-grid-scroller"); + if (!scroller) throw new Error("Data grid scroller not found"); + Object.defineProperties(scroller, { + scrollTop: { configurable: true, writable: true, value: 0 }, + scrollWidth: { configurable: true, value: 1200 }, + clientWidth: { configurable: true, value: 600 }, + clientHeight: { configurable: true, value: 500 }, + scrollHeight: { + configurable: true, + get: () => (scroller.classList.contains("has-horizontal-scrollbar") ? 2600 : 500), + }, + }); + + replaceResult({ + columns: ["id", "payload"], + rows: Array.from({ length: 100 }, (_, index) => [index + 1, `row-${index + 1}`]), + affected_rows: 0, + execution_time_ms: 0, + }); + await settle(); + + expect(scroller.classList.contains("has-horizontal-scrollbar")).toBe(true); + expect(scroller.scrollTop).toBe(0); + }); +}); diff --git a/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts b/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts index e4f3ee1fbe..0df4bf7644 100644 --- a/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts +++ b/apps/desktop/src/lib/__tests__/dataGrid/dataGridInfiniteScroll.spec.ts @@ -29,6 +29,10 @@ describe("data grid bottom anchoring", () => { it("does not anchor a user who is away from the bottom", () => { expect(isDataGridAtScrollBottom({ scrollTop: 700, scrollHeight: 1000, clientHeight: 260 })).toBe(false); }); + + it("does not treat an unmeasured non-scrollable grid as bottom-anchored", () => { + expect(isDataGridAtScrollBottom({ scrollTop: 0, scrollHeight: 500, clientHeight: 500 })).toBe(false); + }); }); describe("data grid horizontal restoration", () => { diff --git a/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts b/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts index 484450c2c6..0ce5dcbba6 100644 --- a/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts +++ b/apps/desktop/src/lib/dataGrid/dataGridInfiniteScroll.ts @@ -69,7 +69,7 @@ export function didDataGridInfiniteScrollContextChange(current: readonly string[ export function isDataGridAtScrollBottom(metrics: DataGridScrollMetrics, tolerance = 1): boolean { const maxScrollTop = Math.max(0, metrics.scrollHeight - metrics.clientHeight); - return maxScrollTop - metrics.scrollTop <= tolerance; + return maxScrollTop > tolerance && maxScrollTop - metrics.scrollTop <= tolerance; } export function dataGridBottomScrollTop(metrics: Pick): number {