diff --git a/bun.lock b/bun.lock index d008ac02dc..51aeb1652e 100644 --- a/bun.lock +++ b/bun.lock @@ -1058,27 +1058,26 @@ }, }, "trustedDependencies": [ - "esbuild", "tree-sitter-powershell", - "protobufjs", - "electron", "web-tree-sitter", "tree-sitter-bash", + "esbuild", + "electron", + "protobufjs", ], "patchedDependencies": { + "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", - "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", + "pacote@21.5.0": "patches/pacote@21.5.0.patch", + "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", - "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", + "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", + "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", + "@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch", "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", - "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", - "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", - "solid-js@1.9.10": "patches/solid-js@1.9.10.patch", - "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", - "pacote@21.5.0": "patches/pacote@21.5.0.patch", + "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", }, "overrides": { "@opentui/core": "catalog:", @@ -1113,7 +1112,7 @@ "@solidjs/router": "0.15.4", "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020", "@tailwindcss/vite": "4.1.11", - "@tanstack/solid-virtual": "3.13.28", + "@tanstack/solid-virtual": "3.13.32", "@tsconfig/bun": "1.0.9", "@tsconfig/node22": "22.0.2", "@types/bun": "1.3.13", @@ -2781,9 +2780,9 @@ "@tanstack/solid-query": ["@tanstack/solid-query@5.91.4", "", { "dependencies": { "@tanstack/query-core": "5.91.2" }, "peerDependencies": { "solid-js": "^1.6.0" } }, "sha512-oCEgn8iT7WnF/7ISd7usBpUK1C9EdvQfg8ZUpKNKZ4edVClICZrCX6f3/Bp8ZlwQnL21KLc2rp+CejEuehlRxg=="], - "@tanstack/solid-virtual": ["@tanstack/solid-virtual@3.13.28", "", { "dependencies": { "@tanstack/virtual-core": "3.17.0" }, "peerDependencies": { "solid-js": "^1.3.0" } }, "sha512-kRuOEL5orH/rzGgxNgfgOttsgV6cgrUeupVtrHMITb5p0rZ3hnxhbu/lhKcR9+7x+EJdfUtJIb2CVC85mlw15g=="], + "@tanstack/solid-virtual": ["@tanstack/solid-virtual@3.13.32", "", { "dependencies": { "@tanstack/virtual-core": "3.17.3" }, "peerDependencies": { "solid-js": "^1.3.0" } }, "sha512-yhX4A4Kgn+wyTg6Mmu8+zwoMTwjz4K1ucvLfRJ8f0rPGDDAIqSaf0v6oU0yT9+SvrjmUaZQ0VX7g4byexbhNng=="], - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.0", "", {}, "sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ=="], + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.3", "", {}, "sha512-8Np/TFELpI0ySuJoVmjvOrQYXH/8sTX0Biv9szhFhY39xOdAAY+smrMxjxOum/ux3eM8MUJQsEJ0/R0UpvC8dw=="], "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], diff --git a/package.json b/package.json index 043924143d..cb8a1a5a27 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@opentui/core": "0.4.3", "@opentui/keymap": "0.4.3", "@opentui/solid": "0.4.3", - "@tanstack/solid-virtual": "3.13.28", + "@tanstack/solid-virtual": "3.13.32", "@shikijs/stream": "4.2.0", "ulid": "3.0.1", "@kobalte/core": "0.13.11", @@ -152,10 +152,9 @@ "gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch", "pacote@21.5.0": "patches/pacote@21.5.0.patch", "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "@tanstack/solid-virtual@3.13.28": "patches/@tanstack%2Fsolid-virtual@3.13.28.patch", "@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch", "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", - "@tanstack/virtual-core@3.17.0": "patches/@tanstack%2Fvirtual-core@3.17.0.patch", - "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch" + "effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch", + "@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch" } } diff --git a/packages/app/test-browser/solid-virtual.test.ts b/packages/app/test-browser/solid-virtual.test.ts index 716fa7fa9e..9327eacf42 100644 --- a/packages/app/test-browser/solid-virtual.test.ts +++ b/packages/app/test-browser/solid-virtual.test.ts @@ -1,8 +1,32 @@ import { expect, test } from "bun:test" -import { createVirtualizer, defaultRangeExtractor } from "@tanstack/solid-virtual" +import { createVirtualizer, defaultRangeExtractor, Virtualizer } from "@tanstack/solid-virtual" import { createRoot, createSignal } from "solid-js" import { filterVirtualIndexes } from "@/pages/session/timeline/virtual-items" +test("end anchoring survives consecutive resizes when the first scroll write is clamped", () => { + const writes: { offset: number; adjustments?: number }[] = [] + const virtualizer = new Virtualizer({ + count: 5, + estimateSize: () => 50, + initialOffset: 50, + initialRect: { width: 400, height: 200 }, + anchorTo: "end", + scrollEndThreshold: 1, + getScrollElement: () => null, + scrollToFn: (offset, options) => writes.push({ offset, adjustments: options.adjustments }), + observeElementRect: () => {}, + observeElementOffset: () => {}, + }) + + virtualizer.getTotalSize() + virtualizer.resizeItem(4, 120) + expect(writes).toEqual([{ offset: 50, adjustments: 70 }]) + writes.length = 0 + + virtualizer.resizeItem(4, 200) + expect(writes).toEqual([{ offset: 120, adjustments: 80 }]) +}) + test("reactive count updates preserve measured row sizes", () => { createRoot((dispose) => { const [count, setCount] = createSignal(2) @@ -42,23 +66,26 @@ test("initial rect projects rows before a scroll element connects", () => { }) }) -test("logical scroll offset includes pending measurement adjustments", () => { - createRoot((dispose) => { - const virtualizer = createVirtualizer({ - count: 2, - getScrollElement: () => null, - estimateSize: () => 60, - initialOffset: 100, - initialRect: { width: 800, height: 60 }, - }) - - virtualizer.getTotalSize() - virtualizer.resizeItem(0, 100) - - expect(virtualizer.scrollOffset).toBe(100) - expect(virtualizer.getLogicalScrollOffset()).toBe(140) - dispose() +test("clamps oversized offsets with scroll margin and padding changes", () => { + const options = (paddingEnd: number) => ({ + count: 20, + estimateSize: () => 60, + initialOffset: Number.MAX_SAFE_INTEGER, + initialRect: { width: 800, height: 600 }, + scrollMargin: 64, + paddingEnd, + overscan: 1, + getScrollElement: () => null, + scrollToFn: () => {}, + observeElementRect: () => {}, + observeElementOffset: () => {}, }) + const virtualizer = new Virtualizer(options(64)) + + expect(virtualizer.getVirtualItems().map((item) => item.index)).toEqual([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + + virtualizer.setOptions(options(600)) + expect(virtualizer.getVirtualItems().map((item) => item.index)).toEqual([18, 19]) }) test("stale pinned indexes do not produce missing virtual items after count shrinks", () => { diff --git a/patches/@tanstack%2Fsolid-virtual@3.13.28.patch b/patches/@tanstack%2Fsolid-virtual@3.13.28.patch deleted file mode 100644 index 3b1cda91e1..0000000000 --- a/patches/@tanstack%2Fsolid-virtual@3.13.28.patch +++ /dev/null @@ -1,45 +0,0 @@ -diff --git a/dist/cjs/index.cjs b/dist/cjs/index.cjs -index 7e97823ea769398ccd9cf449b178c77675ed252c..d75183f11421af0e20e4e8a996af99c300ad936d 100644 ---- a/dist/cjs/index.cjs -+++ b/dist/cjs/index.cjs -@@ -39,7 +39,9 @@ function createVirtualizerBase(options) { - (_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync); - } - })); -- virtualizer.measure(); -+ virtualizer._willUpdate(); -+ setVirtualItems(store.reconcile(instance.getVirtualItems(), { key: "index" })); -+ setTotalSize(instance.getTotalSize()); - }); - return virtualizer; - } -diff --git a/dist/esm/index.js b/dist/esm/index.js -index 1d525463775fef3e8ece6ab191061ef9d0a36d73..14c680a2088c49a33959d8118cf32ee599ab83c2 100644 ---- a/dist/esm/index.js -+++ b/dist/esm/index.js -@@ -38,7 +38,9 @@ function createVirtualizerBase(options) { - (_a = options.onChange) == null ? void 0 : _a.call(options, instance2, sync); - } - })); -- virtualizer.measure(); -+ virtualizer._willUpdate(); -+ setVirtualItems(reconcile(instance.getVirtualItems(), { key: "index" })); -+ setTotalSize(instance.getTotalSize()); - }); - return virtualizer; - } -diff --git a/src/index.tsx b/src/index.tsx -index 69ac34fd70753b9bd00683c2540be7f62630f8f2..9f16672aa0f4a044aa2b35754d385d7d8031f743 100644 ---- a/src/index.tsx -+++ b/src/index.tsx -@@ -81,7 +81,9 @@ function createVirtualizerBase< - }, - }), - ) -- virtualizer.measure() -+ virtualizer._willUpdate() -+ setVirtualItems(reconcile(instance.getVirtualItems(), { key: 'index' })) -+ setTotalSize(instance.getTotalSize()) - }) - - return virtualizer diff --git a/patches/@tanstack%2Fvirtual-core@3.17.0.patch b/patches/@tanstack%2Fvirtual-core@3.17.0.patch deleted file mode 100644 index 58616e31e6..0000000000 --- a/patches/@tanstack%2Fvirtual-core@3.17.0.patch +++ /dev/null @@ -1,105 +0,0 @@ -diff --git a/dist/cjs/index.cjs b/dist/cjs/index.cjs -index df75d0cf0347b62906e04e454d4f4ef062ed5c48..e5110715b049c6c8b992bd1375a7432df7c0182e 100644 ---- a/dist/cjs/index.cjs -+++ b/dist/cjs/index.cjs -@@ -526,6 +526,7 @@ class Virtualizer { - this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset); - return this.scrollOffset; - }; -+ this.getLogicalScrollOffset = () => this.getScrollOffset() + this.scrollAdjustments; - this.getFurthestMeasurement = (measurements, index) => { - const furthestMeasurementsFound = /* @__PURE__ */ new Map(); - const furthestMeasurements = /* @__PURE__ */ new Map(); -@@ -715,10 +716,12 @@ class Virtualizer { - this.options.lanes - ], - (measurements, outerSize, scrollOffset, lanes) => { -+ const maxScrollOffset = Math.max(this.getTotalSize() - outerSize, 0); -+ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset); - return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({ - measurements, - outerSize, -- scrollOffset, -+ scrollOffset: effectiveScrollOffset, - lanes, - // Pass the typed array so binary search + forward-walk can - // read start/end directly from Float64Array, skipping the -diff --git a/dist/cjs/index.d.cts b/dist/cjs/index.d.cts -index c61ee17752565253f795c7fc7d57e86237ecbb52..705bb7e3a121b040fb1a3e7890179eaa3e9b219e 100644 ---- a/dist/cjs/index.d.cts -+++ b/dist/cjs/index.d.cts -@@ -108,6 +108,7 @@ export declare class Virtualizer number; - private scrollAdjustments; - private _iosDeferredAdjustment; - private _iosTouching; -diff --git a/dist/esm/index.d.ts b/dist/esm/index.d.ts -index b03abab604eb6578f6f56ff92c489259cfaf8f19..0495f372ea000dffc416c4f56809946f7ba73099 100644 ---- a/dist/esm/index.d.ts -+++ b/dist/esm/index.d.ts -@@ -108,6 +108,7 @@ export declare class Virtualizer number; - private scrollAdjustments; - private _iosDeferredAdjustment; - private _iosTouching; -diff --git a/dist/esm/index.js b/dist/esm/index.js -index e384cf7541978a2782b9dca68146e869b16ac3f2..77af22006325377bf9ca0052a3554f881f9e75fe 100644 ---- a/dist/esm/index.js -+++ b/dist/esm/index.js -@@ -524,6 +524,7 @@ class Virtualizer { - this.scrollOffset = this.scrollOffset ?? (typeof this.options.initialOffset === "function" ? this.options.initialOffset() : this.options.initialOffset); - return this.scrollOffset; - }; -+ this.getLogicalScrollOffset = () => this.getScrollOffset() + this.scrollAdjustments; - this.getFurthestMeasurement = (measurements, index) => { - const furthestMeasurementsFound = /* @__PURE__ */ new Map(); - const furthestMeasurements = /* @__PURE__ */ new Map(); -@@ -713,10 +714,12 @@ class Virtualizer { - this.options.lanes - ], - (measurements, outerSize, scrollOffset, lanes) => { -+ const maxScrollOffset = Math.max(this.getTotalSize() - outerSize, 0); -+ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset); - return this.range = measurements.length > 0 && outerSize > 0 ? calculateRange({ - measurements, - outerSize, -- scrollOffset, -+ scrollOffset: effectiveScrollOffset, - lanes, - // Pass the typed array so binary search + forward-walk can - // read start/end directly from Float64Array, skipping the -diff --git a/src/index.ts b/src/index.ts -index d35b3e0695a9c85b261bc1a4fbe23c0a60d5b204..c504e630a60a3a7791345622bb02754b1308fd86 100644 ---- a/src/index.ts -+++ b/src/index.ts -@@ -1047,6 +1047,8 @@ export class Virtualizer< - return this.scrollOffset - } - -+ getLogicalScrollOffset = () => this.getScrollOffset() + this.scrollAdjustments -+ - private getFurthestMeasurement = ( - measurements: Array, - index: number, -@@ -1334,12 +1336,14 @@ export class Virtualizer< - this.options.lanes, - ], - (measurements, outerSize, scrollOffset, lanes) => { -+ const maxScrollOffset = Math.max(this.getTotalSize() - outerSize, 0) -+ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset) - return (this.range = - measurements.length > 0 && outerSize > 0 - ? calculateRange({ - measurements, - outerSize, -- scrollOffset, -+ scrollOffset: effectiveScrollOffset, - lanes, - // Pass the typed array so binary search + forward-walk can - // read start/end directly from Float64Array, skipping the diff --git a/patches/@tanstack%2Fvirtual-core@3.17.3.patch b/patches/@tanstack%2Fvirtual-core@3.17.3.patch new file mode 100644 index 0000000000..2450d4234b --- /dev/null +++ b/patches/@tanstack%2Fvirtual-core@3.17.3.patch @@ -0,0 +1,108 @@ +diff --git a/dist/cjs/index.cjs b/dist/cjs/index.cjs +index 52ae6ca12f8d1c650ee7f1bd55573ee7d4f8b65f..830f646f5bd80f4e00f442291ad6e4c6c508630d 100644 +--- a/dist/cjs/index.cjs ++++ b/dist/cjs/index.cjs +@@ -716,17 +716,20 @@ class Virtualizer { + this.getMeasurements(), + this.getSize(), + this.getScrollOffset(), +- this.options.lanes ++ this.options.lanes, ++ this.options.paddingEnd + ], +- (measurements, outerSize, scrollOffset, lanes) => { ++ (measurements, outerSize, scrollOffset, lanes, _paddingEnd) => { + if (measurements.length === 0 || outerSize === 0) { + this.range = null; + return null; + } ++ const maxScrollOffset = Math.max(this.options.scrollMargin + this.getTotalSize() - outerSize, 0); ++ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset); + this.range = calculateRangeImpl( + measurements, + outerSize, +- scrollOffset, ++ effectiveScrollOffset, + lanes, + // Pass the typed array so binary search + forward-walk can read + // start/end directly from Float64Array, skipping the Proxy traps. +diff --git a/dist/cjs/index.d.cts b/dist/cjs/index.d.cts +index c61ee17752565253f795c7fc7d57e86237ecbb52..430c622a18c69407df9f1ca82ee9440e9be287b7 100644 +--- a/dist/cjs/index.d.cts ++++ b/dist/cjs/index.d.cts +@@ -144,7 +144,7 @@ export declare class Virtualizer { ++ (measurements, outerSize, scrollOffset, lanes, _paddingEnd) => { + if (measurements.length === 0 || outerSize === 0) { + this.range = null; + return null; + } ++ const maxScrollOffset = Math.max(this.options.scrollMargin + this.getTotalSize() - outerSize, 0); ++ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset); + this.range = calculateRangeImpl( + measurements, + outerSize, +- scrollOffset, ++ effectiveScrollOffset, + lanes, + // Pass the typed array so binary search + forward-walk can read + // start/end directly from Float64Array, skipping the Proxy traps. +diff --git a/src/index.ts b/src/index.ts +index 7ad79aacd591c3a4f7855c91b2727a59a9579bca..201406779ee85d2f2e5b01a8ea284aa9a5f694e2 100644 +--- a/src/index.ts ++++ b/src/index.ts +@@ -1367,16 +1367,19 @@ export class Virtualizer< + this.getSize(), + this.getScrollOffset(), + this.options.lanes, ++ this.options.paddingEnd, + ], +- (measurements, outerSize, scrollOffset, lanes) => { ++ (measurements, outerSize, scrollOffset, lanes, _paddingEnd) => { + if (measurements.length === 0 || outerSize === 0) { + this.range = null + return null + } ++ const maxScrollOffset = Math.max(this.options.scrollMargin + this.getTotalSize() - outerSize, 0) ++ const effectiveScrollOffset = Math.min(Math.max(scrollOffset, 0), maxScrollOffset) + this.range = calculateRangeImpl( + measurements, + outerSize, +- scrollOffset, ++ effectiveScrollOffset, + lanes, + // Pass the typed array so binary search + forward-walk can read + // start/end directly from Float64Array, skipping the Proxy traps.