From 341b9851b5edbe64bf3672d14843f4aa96900e3c Mon Sep 17 00:00:00 2001 From: mintaka Date: Tue, 18 Aug 2026 20:56:38 -0400 Subject: [PATCH 1/4] feat: Solid 2 support --- packages/solid-virtual/package.json | 6 +- packages/solid-virtual/src/index.tsx | 79 +++--- packages/solid-virtual/tests/index.test.ts | 10 +- pnpm-lock.yaml | 264 ++++++++++++++++----- 4 files changed, 260 insertions(+), 99 deletions(-) diff --git a/packages/solid-virtual/package.json b/packages/solid-virtual/package.json index bdccd637c..d42282692 100644 --- a/packages/solid-virtual/package.json +++ b/packages/solid-virtual/package.json @@ -57,10 +57,10 @@ "@tanstack/virtual-core": "workspace:*" }, "devDependencies": { - "solid-js": "^1.9.7", - "vite-plugin-solid": "^2.11.6" + "solid-js": "2.0.0-rc.0", + "vite-plugin-solid": "3.0.0-next.27" }, "peerDependencies": { - "solid-js": "^1.3.0" + "solid-js": "^2.0.0-rc.0" } } diff --git a/packages/solid-virtual/src/index.tsx b/packages/solid-virtual/src/index.tsx index 9f16672aa..4f25b8754 100644 --- a/packages/solid-virtual/src/index.tsx +++ b/packages/solid-virtual/src/index.tsx @@ -9,13 +9,13 @@ import { } from '@tanstack/virtual-core' import { - createComputed, + createEffect, createSignal, - mergeProps, - onCleanup, - onMount, + createStore, + merge, + onSettled, + reconcile, } from 'solid-js' -import { createStore, reconcile } from 'solid-js/store' import type { PartialKeys, VirtualizerOptions } from '@tanstack/virtual-core' export * from '@tanstack/virtual-core' @@ -27,7 +27,7 @@ function createVirtualizerBase< options: VirtualizerOptions, ): Virtualizer { const resolvedOptions: VirtualizerOptions = - mergeProps(options) + merge(options) const instance = new Virtualizer( resolvedOptions, @@ -36,7 +36,13 @@ function createVirtualizerBase< const [virtualItems, setVirtualItems] = createStore( instance.getVirtualItems(), ) - const [totalSize, setTotalSize] = createSignal(instance.getTotalSize()) + const [totalSize, setTotalSize] = createSignal(instance.getTotalSize(), { + // virtual-core drives these bridge signals from its imperative API + // (resizeItem, measure, scroll handlers), which consumers may call from + // an owned scope. The write is the adapter's own internal notify bridge, + // not app state escaping a computation, so opt in to owned writes. + ownedWrite: true, + }) const handler = { get( @@ -57,34 +63,41 @@ function createVirtualizerBase< const virtualizer = new Proxy(instance, handler) virtualizer.setOptions(resolvedOptions) - onMount(() => { + onSettled(() => { const cleanup = virtualizer._didMount() virtualizer._willUpdate() - onCleanup(cleanup) + return cleanup }) - createComputed(() => { - virtualizer.setOptions( - mergeProps(resolvedOptions, options, { - onChange: ( - instance: Virtualizer, - sync: boolean, - ) => { - instance._willUpdate() - setVirtualItems( - reconcile(instance.getVirtualItems(), { - key: 'index', - }), - ) - setTotalSize(instance.getTotalSize()) - options.onChange?.(instance, sync) - }, - }), - ) - virtualizer._willUpdate() - setVirtualItems(reconcile(instance.getVirtualItems(), { key: 'index' })) - setTotalSize(instance.getTotalSize()) - }) + createEffect( + () => { + // Compute phase: (re)apply options, tracking every reactive getter + // read inside `setOptions`. No store/signal writes happen here — those + // are forbidden inside a reactive scope in v2. The `onChange` callback + // installed below fires from event/observer context, where writes are + // legal. + virtualizer.setOptions( + merge(resolvedOptions, options, { + onChange: ( + instance: Virtualizer, + sync: boolean, + ) => { + instance._willUpdate() + setVirtualItems(reconcile(instance.getVirtualItems(), 'index')) + setTotalSize(instance.getTotalSize()) + options.onChange?.(instance, sync) + }, + }), + ) + }, + () => { + // Apply phase: run the layout update and push the results into the + // store/signal. Writes are permitted here. + virtualizer._willUpdate() + setVirtualItems(reconcile(instance.getVirtualItems(), 'index')) + setTotalSize(instance.getTotalSize()) + }, + ) return virtualizer } @@ -99,7 +112,7 @@ export function createVirtualizer< >, ): Virtualizer { return createVirtualizerBase( - mergeProps( + merge( { observeElementRect: observeElementRect, observeElementOffset: observeElementOffset, @@ -120,7 +133,7 @@ export function createWindowVirtualizer( >, ): Virtualizer { return createVirtualizerBase( - mergeProps( + merge( { getScrollElement: () => typeof document !== 'undefined' ? window : null, diff --git a/packages/solid-virtual/tests/index.test.ts b/packages/solid-virtual/tests/index.test.ts index 37989150d..c1f0f585e 100644 --- a/packages/solid-virtual/tests/index.test.ts +++ b/packages/solid-virtual/tests/index.test.ts @@ -1,11 +1,13 @@ import { expect, test } from 'vitest' -import { createRoot, createSignal } from 'solid-js' +import { createRoot, createSignal, flush } from 'solid-js' import { createVirtualizer } from '../src/index' test('preserves measured sizes when reactive options change', () => { createRoot((dispose) => { - const [count, setCount] = createSignal(2) + // In an app the count signal is written from an event handler (an + // unowned scope); here the write happens inside createRoot, so opt in. + const [count, setCount] = createSignal(2, { ownedWrite: true }) const virtualizer = createVirtualizer({ get count() { return count() @@ -17,9 +19,13 @@ test('preserves measured sizes when reactive options change', () => { expect(virtualizer.getTotalSize()).toBe(120) virtualizer.resizeItem(0, 100) + // v2 setters land on the next microtask flush; force it so the read below + // observes the resize synchronously. + flush() expect(virtualizer.getTotalSize()).toBe(160) setCount(3) + flush() expect(virtualizer.itemSizeCache.get(0)).toBe(100) expect(virtualizer.getTotalSize()).toBe(220) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b90143c4..e46921973 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2111,11 +2111,11 @@ importers: version: link:../virtual-core devDependencies: solid-js: - specifier: ^1.9.7 - version: 1.9.10 + specifier: 2.0.0-rc.0 + version: 2.0.0-rc.0 vite-plugin-solid: - specifier: ^2.11.6 - version: 2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)) + specifier: 3.0.0-next.27 + version: 3.0.0-next.27(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-rc.0)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)) packages/svelte-virtual: dependencies: @@ -3145,15 +3145,60 @@ packages: resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} engines: {node: '>=14.17.0'} + '@dom-expressions/babel-plugin-jsx@0.50.0-next.42': + resolution: {integrity: sha512-ol24x9RW8loPyOTzC/mQzh/zAsrsPxyTG4WRxxRlwzNK2uBWIBftWN5IwmSV51zDQa7JcT6sE6kkXFCLUvsYIQ==} + peerDependencies: + '@babel/core': ^7.20.12 + + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.40': + resolution: {integrity: sha512-+3aXdhw4SVt08fvgAsEM56ky/wdCVPXT0Wtxo164Cchgg7sapPsRqo8Gwwn0UU5AhVcEp3CIhdB5+pMoUicKFg==} + cpu: [arm64] + os: [darwin] + + '@dom-expressions/compiler-darwin-x64@0.50.0-next.40': + resolution: {integrity: sha512-Tbjg6ZQEIhKLKGd/Ep6MKqD76arPdV6SE2d3fkrP4qooIv2gYvAkvUw90qNdZxVHcuyjutrbJE0WpXYY9nSIIQ==} + cpu: [x64] + os: [darwin] + + '@dom-expressions/compiler-linux-arm64-gnu@0.50.0-next.40': + resolution: {integrity: sha512-tIJMY8dPyjiYNSL5uc4JA5l88Sw0WoMwoAilqTTHpGYdCL5GwzGq6ZOV1bndw8XKEYFYfnTr276FDHyYpLtRYQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@dom-expressions/compiler-linux-x64-gnu@0.50.0-next.40': + resolution: {integrity: sha512-H/K/3Ykk8aCSNuKDlv/sgbPdSks+zqFxZ4mzqaGJmlDqEfeaWYXan5fsvwQbACdNDJeKoKLO4GhnlnKlKWjiJg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@dom-expressions/compiler-wasm32-wasi@0.50.0-next.40': + resolution: {integrity: sha512-2SAfc35FEvvxkUz2yeh/4aNx0s9waZnce4KS64oUMlVpYEFtNBdfnZKCB/bgzJFvTxXjMs+HcU6OInGltH5jHA==} + engines: {node: '>=14.0.0'} + + '@dom-expressions/compiler-win32-x64-msvc@0.50.0-next.40': + resolution: {integrity: sha512-bBdHMxdfUHtIrS5xrt9udqdGRnGKdYQgKxJNIts+Il3Fs8QGyNwaZpi0tjnlRjYa0+GdWEC3Gw1Pmq/HFXzFeQ==} + cpu: [x64] + os: [win32] + + '@dom-expressions/compiler@0.50.0-next.40': + resolution: {integrity: sha512-RI/kHU+QkLOHo4CQyqLTn9c7sAfl/GEULMsOpS1YqkPJgy7R8MCPWXFN4sI0QDBbM6ePtEyuW+2bsdsXqyxkzQ==} + '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/core@1.6.0': resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==} '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.6.0': resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==} @@ -4882,6 +4927,25 @@ packages: '@sinclair/typebox@0.34.41': resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@solidjs/signals@2.0.0-rc.0': + resolution: {integrity: sha512-oKZSfvsCcKw1uJjOGbUkJ+OqlhXLHtZ+rShSyu9KH0lUH7UUwfMfsKeh81JPiQxDDg4YLhEwI38hg0JkwzTdvA==} + + '@solidjs/vite-plugin@3.0.0-next.28': + resolution: {integrity: sha512-P/Xova2R8QoveQ2szrzkHSMPZjIyc5dE4hh2UHNgRW8CC5sSoh8yzPLw7qwvKdE1DydPHUWq/hrRIApkwt+grw==} + peerDependencies: + '@solidjs/web': ^2.0.0-rc.0 + '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* + solid-js: ^2.0.0-rc.0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@testing-library/jest-dom': + optional: true + + '@solidjs/web@2.0.0-rc.0': + resolution: {integrity: sha512-pYSaA9+dH8H1h/d/ZF/P2kR6omfzFGNcdzKhWTcg9fJghXhn8+5UrXUr2iYxDdYNOXZzxxFQhYHSJ7P4HKDqgw==} + peerDependencies: + solid-js: ^2.0.0-rc.0 + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -5789,11 +5853,6 @@ packages: '@babel/core': ^7.12.0 webpack: '>=5.61.0' - babel-plugin-jsx-dom-expressions@0.40.3: - resolution: {integrity: sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==} - peerDependencies: - '@babel/core': ^7.20.12 - babel-plugin-polyfill-corejs2@0.4.14: resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} peerDependencies: @@ -5812,11 +5871,11 @@ packages: babel-plugin-react-compiler@1.0.0: resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} - babel-preset-solid@1.9.10: - resolution: {integrity: sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==} + babel-preset-solid@2.0.0-rc.0: + resolution: {integrity: sha512-Ap2/QQY3pICj+Q0VM/RnIOpZo7e6icZnUA0oBJuhqzoCrljqMNo3eFb2OeEa4pUQeFREJOlex4Bt1ggwrcgC8w==} peerDependencies: '@babel/core': ^7.0.0 - solid-js: ^1.9.10 + solid-js: ^2.0.0-rc.0 peerDependenciesMeta: solid-js: optional: true @@ -8716,10 +8775,20 @@ packages: peerDependencies: seroval: ^1.0 + seroval-plugins@1.5.6: + resolution: {integrity: sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + seroval@1.3.2: resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} engines: {node: '>=10'} + seroval@1.5.6: + resolution: {integrity: sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA==} + engines: {node: '>=10'} + serve-index@1.9.1: resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} engines: {node: '>= 0.8.0'} @@ -8866,10 +8935,8 @@ packages: solid-js@1.9.10: resolution: {integrity: sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==} - solid-refresh@0.6.3: - resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} - peerDependencies: - solid-js: ^1.3 + solid-js@2.0.0-rc.0: + resolution: {integrity: sha512-3enTJ71VL69nM5p/it2InVBDBt316Cqfij+F0S7VuHZLITc8YV7Rvavjoy52nAKKxYWXM+BcXh2K7KcLTf1zdQ==} source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} @@ -9297,6 +9364,9 @@ packages: deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true + validate-html-nesting@1.2.4: + resolution: {integrity: sha512-doQi7e8EJ2OWneSG1aZpJluS6A49aZM0+EICXWKm1i6WvqTLmq0tpUcImc4KTWG50mORO0C4YDBtOCSYvElftw==} + validate-npm-package-name@6.0.2: resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -9340,15 +9410,8 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - vite-plugin-solid@2.11.10: - resolution: {integrity: sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==} - peerDependencies: - '@testing-library/jest-dom': ^5.16.6 || ^5.17.0 || ^6.* - solid-js: ^1.7.2 - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - peerDependenciesMeta: - '@testing-library/jest-dom': - optional: true + vite-plugin-solid@3.0.0-next.27: + resolution: {integrity: sha512-bDzjIIplkSDH73BiGP9pbPR3ZnjeUA18SAYugLhqDCy4u0bl3qdrETstxrXuo2vHXLB0VD9rvOr0iesZBBul4Q==} vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} @@ -10606,9 +10669,9 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.7)': @@ -11317,12 +11380,59 @@ snapshots: '@discoveryjs/json-ext@0.6.3': {} + '@dom-expressions/babel-plugin-jsx@0.50.0-next.42(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.7) + '@babel/types': 7.29.7 + html-entities: 2.3.3 + parse5: 7.3.0 + validate-html-nesting: 1.2.4 + + '@dom-expressions/compiler-darwin-arm64@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-darwin-x64@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-linux-arm64-gnu@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-linux-x64-gnu@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler-wasm32-wasi@0.50.0-next.40': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@dom-expressions/compiler-win32-x64-msvc@0.50.0-next.40': + optional: true + + '@dom-expressions/compiler@0.50.0-next.40': + optionalDependencies: + '@dom-expressions/compiler-darwin-arm64': 0.50.0-next.40 + '@dom-expressions/compiler-darwin-x64': 0.50.0-next.40 + '@dom-expressions/compiler-linux-arm64-gnu': 0.50.0-next.40 + '@dom-expressions/compiler-linux-x64-gnu': 0.50.0-next.40 + '@dom-expressions/compiler-wasm32-wasi': 0.50.0-next.40 + '@dom-expressions/compiler-win32-x64-msvc': 0.50.0-next.40 + '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/core@1.6.0': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -11333,6 +11443,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.6.0': dependencies: tslib: 2.8.1 @@ -12165,8 +12280,8 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.6.0 - '@emnapi/runtime': 1.6.0 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 '@tybys/wasm-util': 0.10.1 optional: true @@ -12190,6 +12305,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@ngtools/webpack@20.3.32(@angular/compiler-cli@20.3.26(@angular/compiler@20.3.26)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(esbuild@0.28.1))': dependencies: '@angular/compiler-cli': 20.3.26(@angular/compiler@20.3.26)(typescript@5.9.3) @@ -12803,6 +12925,31 @@ snapshots: '@sinclair/typebox@0.34.41': {} + '@solidjs/signals@2.0.0-rc.0': {} + + '@solidjs/vite-plugin@3.0.0-next.28(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-rc.0)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/core': 7.29.7 + '@dom-expressions/compiler': 0.50.0-next.40 + '@solidjs/web': 2.0.0-rc.0(solid-js@2.0.0-rc.0) + '@types/babel__core': 7.20.5 + babel-preset-solid: 2.0.0-rc.0(@babel/core@7.29.7)(solid-js@2.0.0-rc.0) + merge-anything: 5.1.7 + solid-js: 2.0.0-rc.0 + vite: 6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)) + optionalDependencies: + '@testing-library/jest-dom': 6.9.1 + transitivePeerDependencies: + - supports-color + + '@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0)': + dependencies: + seroval: 1.5.6 + seroval-plugins: 1.5.6(seroval@1.5.6) + solid-js: 2.0.0-rc.0 + '@standard-schema/spec@1.1.0': {} '@stylistic/eslint-plugin@5.5.0(eslint@9.39.0(jiti@2.6.1)(supports-color@10.2.2))': @@ -13969,15 +14116,6 @@ snapshots: find-up: 5.0.0 webpack: 5.105.0(esbuild@0.28.1) - babel-plugin-jsx-dom-expressions@0.40.3(@babel/core@7.28.5): - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.18.6 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/types': 7.28.5 - html-entities: 2.3.3 - parse5: 7.3.0 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.29.7): dependencies: '@babel/compat-data': 7.29.7 @@ -14006,12 +14144,12 @@ snapshots: dependencies: '@babel/types': 7.28.5 - babel-preset-solid@1.9.10(@babel/core@7.28.5)(solid-js@1.9.10): + babel-preset-solid@2.0.0-rc.0(@babel/core@7.29.7)(solid-js@2.0.0-rc.0): dependencies: - '@babel/core': 7.28.5 - babel-plugin-jsx-dom-expressions: 0.40.3(@babel/core@7.28.5) + '@babel/core': 7.29.7 + '@dom-expressions/babel-plugin-jsx': 0.50.0-next.42(@babel/core@7.29.7) optionalDependencies: - solid-js: 1.9.10 + solid-js: 2.0.0-rc.0 balanced-match@1.0.2: {} @@ -17181,8 +17319,16 @@ snapshots: seroval-plugins@1.3.3(seroval@1.3.2): dependencies: seroval: 1.3.2 + optional: true + + seroval-plugins@1.5.6(seroval@1.5.6): + dependencies: + seroval: 1.5.6 + + seroval@1.3.2: + optional: true - seroval@1.3.2: {} + seroval@1.5.6: {} serve-index@1.9.1(supports-color@10.2.2): dependencies: @@ -17356,15 +17502,14 @@ snapshots: csstype: 3.2.3 seroval: 1.3.2 seroval-plugins: 1.3.3(seroval@1.3.2) + optional: true - solid-refresh@0.6.3(solid-js@1.9.10): + solid-js@2.0.0-rc.0: dependencies: - '@babel/generator': 7.29.7 - '@babel/helper-module-imports': 7.27.1 - '@babel/types': 7.28.5 - solid-js: 1.9.10 - transitivePeerDependencies: - - supports-color + '@solidjs/signals': 2.0.0-rc.0 + csstype: 3.2.3 + seroval: 1.5.6 + seroval-plugins: 1.5.6(seroval@1.5.6) source-map-js@1.2.1: {} @@ -17782,6 +17927,8 @@ snapshots: uuid@8.3.2: {} + validate-html-nesting@1.2.4: {} + validate-npm-package-name@6.0.2: {} vary@1.1.2: {} @@ -17817,20 +17964,15 @@ snapshots: dependencies: vite: 6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1) - vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.9.1)(solid-js@1.9.10)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)): + vite-plugin-solid@3.0.0-next.27(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-rc.0)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)): dependencies: - '@babel/core': 7.28.5 - '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.10(@babel/core@7.28.5)(solid-js@1.9.10) - merge-anything: 5.1.7 - solid-js: 1.9.10 - solid-refresh: 0.6.3(solid-js@1.9.10) - vite: 6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1) - vitefu: 1.1.1(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)) - optionalDependencies: - '@testing-library/jest-dom': 6.9.1 + '@solidjs/vite-plugin': 3.0.0-next.28(@solidjs/web@2.0.0-rc.0(solid-js@2.0.0-rc.0))(@testing-library/jest-dom@6.9.1)(solid-js@2.0.0-rc.0)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)) transitivePeerDependencies: + - '@solidjs/web' + - '@testing-library/jest-dom' + - solid-js - supports-color + - vite vite-tsconfig-paths@5.1.4(supports-color@10.2.2)(typescript@5.9.3)(vite@6.4.2(@types/node@24.9.2)(jiti@2.6.1)(less@4.4.0)(lightningcss@1.33.0)(sass@1.90.0)(terser@5.43.1)(yaml@2.8.1)): dependencies: From 6df0ac7b444ea090cd507804679020e680e92800 Mon Sep 17 00:00:00 2001 From: mintaka Date: Sat, 22 Aug 2026 21:02:17 -0400 Subject: [PATCH 2/4] feat(solid-virtual): publish as @rigelbuild/solid-virtual with a fork-owned workflow (RIG-2187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publish a Solid-2-compatible `@tanstack/solid-virtual` fork to npm as `@rigelbuild/solid-virtual`, so `compass` can drop its unresolvable `github:` pin (`github:RigelBuild/virtual#solid-2-support` resolves the monorepo root — no package export) and consume a real published package on the Solid-1→2 migration (RIG-2187). Frozen design record: `docs/designs/platform/rigelbuild-solid-virtual-publish.md` (orion, DL-015). ## What this branch contains Two commits, kept separate so the Solid-2 work stays upstreamable to `TanStack/virtual`: - **`feat: Solid 2 support`** — the Solid-2 API port (`packages/solid-virtual/{src,tests,package.json}` + lockfile), unchanged from the `solid-2-support` branch. Kept as its own commit so it stays a clean, contributable diff for the eventual upstream PR (branch from upstream `main` + cherry-pick this commit). - **this commit (publish-only)** — renames the package and wires the fork-owned publish: - `packages/solid-virtual/package.json`: `name` `@tanstack/solid-virtual` → `@rigelbuild/solid-virtual`, `version` → `3.0.0-rc.0`. The `@tanstack/virtual-core` dep stays `workspace:*` — `pnpm publish` rewrites the `workspace:` protocol to the resolved range (`3.17.8`) at pack time, so no committed dep edit and no republish of `virtual-core` (it is unchanged from upstream). `repository`/`funding` metadata left as upstream (honest for a fork; keeps this commit minimal). - `.github/workflows/publish-rigelbuild.yml`: a fork-owned publish gated `if: github.repository_owner == 'RigelBuild'`, firing on a `v*` tag or manual dispatch. Runs the monorepo `test:ci` (nx `run-many`, not `affected` — a tag push has no affected base, so `affected` would run nothing and could ship an unbuilt dist; `run-many` builds every package in dependency order), then a direct `pnpm publish --filter @rigelbuild/solid-virtual --tag --no-git-checks`. The dist-tag is computed from the version (prerelease → `next`, GA → `latest`), mirroring the solid-markdown fork's `publish.yml`. Authenticated by the `NPM_TOKEN` Actions secret provisioned via orion Pulumi (separate PR). Upstream `release.yml` (gated `if: github.repository_owner == 'TanStack'`) is left untouched. `changeset publish` is deliberately not used: it ignores `config.ignore`, rejects ignoring `virtual-core` while a non-ignored package depends on it, and mis-tags a prerelease as `latest` — the direct filtered publish scopes the one renamed package cleanly while still reusing the pnpm monorepo build/test infra (frozen record D2). The publish itself (tag + workflow run + npm verify) is a follow-up once this and the orion token PR merge. Refs RIG-2187 Co-authored-by: Matt Wilkinson --- .github/workflows/publish-rigelbuild.yml | 62 ++++++++++++++++++++++++ packages/solid-virtual/package.json | 4 +- 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/publish-rigelbuild.yml diff --git a/.github/workflows/publish-rigelbuild.yml b/.github/workflows/publish-rigelbuild.yml new file mode 100644 index 000000000..806e82ead --- /dev/null +++ b/.github/workflows/publish-rigelbuild.yml @@ -0,0 +1,62 @@ +name: Publish (RigelBuild) + +# Fork-owned publish for @rigelbuild/solid-virtual. Upstream release.yml is +# gated `if: github.repository_owner == 'TanStack'` and stays untouched +# (upstreamable); this workflow is gated to the fork owner and publishes only +# the one renamed package via a direct filtered `pnpm publish`, computing the +# dist-tag from the version (prerelease -> next, GA -> latest). It reuses the +# monorepo install/build/test tooling; only the changesets *publish step* is +# bypassed, since it cannot scope a single renamed package's publish here. + +permissions: + contents: read + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + publish: + name: Publish + if: github.repository_owner == 'RigelBuild' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Setup Tools + uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3 # main + - name: Install Playwright browsers + run: pnpm exec playwright install chromium + - name: Build and test the workspace + # `test:ci` = nx run-many (not `affected`): a tag push has no affected + # base, so `affected` would run nothing and could publish an unbuilt + # dist. run-many runs every package's checks + the `build` target in + # dependency order (virtual-core before solid-virtual), so the dist + # this job publishes is freshly built and verified. Mirrors pr.yml. + run: pnpm run test:ci + - name: Resolve npm dist-tag from version + id: disttag + run: | + version=$(node -p "require('./packages/solid-virtual/package.json').version") + if [[ "$version" == *-* ]]; then + echo "tag=next" >> "$GITHUB_OUTPUT" + else + echo "tag=latest" >> "$GITHUB_OUTPUT" + fi + - name: Publish @rigelbuild/solid-virtual + # dist-tag passed through env, not interpolated into the run string, so + # the `${{ }}` expansion can't reach the shell command line (zizmor + # template-injection). Auth is the NPM_TOKEN secret, not npm trusted + # publishing (OIDC): the token is provisioned + custodied via the orion + # Pulumi github stack per the frozen design record (rigelbuild-solid- + # virtual-publish, DL-015), so the use-trusted-publishing audit is + # deliberately ignored on the publish line below. + run: pnpm publish --filter @rigelbuild/solid-virtual --tag "$TAG" --no-git-checks # zizmor: ignore[use-trusted-publishing] + env: + TAG: ${{ steps.disttag.outputs.tag }} + NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/packages/solid-virtual/package.json b/packages/solid-virtual/package.json index d42282692..1175075d9 100644 --- a/packages/solid-virtual/package.json +++ b/packages/solid-virtual/package.json @@ -1,6 +1,6 @@ { - "name": "@tanstack/solid-virtual", - "version": "3.13.37", + "name": "@rigelbuild/solid-virtual", + "version": "3.0.0-rc.0", "description": "Headless UI for virtualizing scrollable elements in Solid", "author": "Tanner Linsley", "license": "MIT", From 7f02ff92fa06061d0d9f4e50e4875c07d99235dd Mon Sep 17 00:00:00 2001 From: mintaka Date: Sat, 22 Aug 2026 21:39:21 -0400 Subject: [PATCH 3/4] fix(solid-virtual): make the publish workflow actually authenticate + publish (RIG-2187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fold review findings on the fork-owned publish workflow — three runtime-only failures a local build can't surface, all in the publish/auth plumbing: - **HIGH — provenance needs `id-token: write`.** The fork root `.npmrc` sets `provenance=true`, so `pnpm publish` generates an npm provenance attestation signed via GitHub OIDC, which requires the job to hold `id-token: write`. The job had only `contents: read`, so the first `v*` tag would fail at the publish step. Added job-level `permissions: { contents: read, id-token: write }`, mirroring upstream `release.yml`. The NPM_TOKEN still authenticates the upload; provenance is the attestation, not the auth (this is not trusted publishing). - **MEDIUM — first scoped publish needs public access.** `@rigelbuild/solid-virtual` is a new scoped package; npm defaults scoped packages to `restricted`, so the first publish would 402 (or publish privately, breaking the compass consumer contract). Added `"publishConfig": { "access": "public" }` to `packages/solid-virtual/package.json`, matching the proven `@rigelbuild/solid-markdown` precedent. - **MEDIUM — pnpm auth was wired to inert env vars.** `NPM_CONFIG_TOKEN` maps to the legacy global `token` config registry.npmjs.org no longer accepts, and `NODE_AUTH_TOKEN` is a setup-node convention pnpm doesn't read natively — so the publish would 401/ENEEDAUTH. Replaced both with the per-registry URL-scoped env var `pnpm_config_//registry.npmjs.org/:_authToken`, which pnpm (>=11.6) honors natively: file-free, and un-redirectable since the registry is baked into the key. Spec-impact: none Refs RIG-2187 Co-authored-by: Matt Wilkinson --- .github/workflows/publish-rigelbuild.yml | 16 ++++++++++++++-- packages/solid-virtual/package.json | 3 +++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-rigelbuild.yml b/.github/workflows/publish-rigelbuild.yml index 806e82ead..bed606f57 100644 --- a/.github/workflows/publish-rigelbuild.yml +++ b/.github/workflows/publish-rigelbuild.yml @@ -22,6 +22,13 @@ jobs: name: Publish if: github.repository_owner == 'RigelBuild' runs-on: ubuntu-latest + permissions: + contents: read + # The repo root .npmrc sets provenance=true, so `pnpm publish` generates + # an npm provenance attestation, which is signed via GitHub OIDC and needs + # id-token: write (mirrors upstream release.yml). The NPM_TOKEN below still + # authenticates the upload — provenance is an attestation, not the auth. + id-token: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -58,5 +65,10 @@ jobs: run: pnpm publish --filter @rigelbuild/solid-virtual --tag "$TAG" --no-git-checks # zizmor: ignore[use-trusted-publishing] env: TAG: ${{ steps.disttag.outputs.tag }} - NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # pnpm authenticates via a per-registry _authToken, not the legacy + # global `token` (NPM_CONFIG_TOKEN) which registry.npmjs.org no longer + # accepts, nor NODE_AUTH_TOKEN (a setup-node convention pnpm doesn't + # read). The URL-scoped env var is honored natively by pnpm (>=11.6), + # is file-free, and can't be redirected to another host since the + # registry is baked into the key. + pnpm_config_//registry.npmjs.org/:_authToken: ${{ secrets.NPM_TOKEN }} diff --git a/packages/solid-virtual/package.json b/packages/solid-virtual/package.json index 1175075d9..84de80a8c 100644 --- a/packages/solid-virtual/package.json +++ b/packages/solid-virtual/package.json @@ -1,6 +1,9 @@ { "name": "@rigelbuild/solid-virtual", "version": "3.0.0-rc.0", + "publishConfig": { + "access": "public" + }, "description": "Headless UI for virtualizing scrollable elements in Solid", "author": "Tanner Linsley", "license": "MIT", From 3aad1bff7c3c1b04d6459b38397b1e746c0a013f Mon Sep 17 00:00:00 2001 From: mintaka Date: Sat, 22 Aug 2026 22:06:36 -0400 Subject: [PATCH 4/4] fix(solid-virtual): publish without provenance to match the fork's upstream-pointed manifest (RIG-2187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round-2 review HIGH: the previous review-fix added `id-token: write` to let `pnpm publish` sign a provenance attestation, but the package's `repository.url` points at the upstream `TanStack/virtual` repo (frozen in DL-015 as "honest for a fork"), while the workflow publishes from `RigelBuild/virtual`. npm provenance requires `repository.url` to match the publishing repo (OIDC-derived) or the registry rejects the upload (422/EPROVENANCE) — so the first `v*` tag would fail at publish. The design-consistent resolution is to publish WITHOUT provenance, exactly like the `@rigelbuild/solid-markdown` precedent (no provenance, no id-token, `repository.url` left as-is), keeping DL-015's frozen decision to leave `repository.url` pointing upstream: - Force provenance off at publish time via `pnpm_config_provenance: "false"` on the Publish step. This is the only surface pnpm 11 reads provenance from: `isNpmrcReadableKey` (pnpm11 config/reader localConfig.ts) admits only auth/network keys, so a `provenance` line in any `.npmrc` (including the repo-root `provenance=true`) is silently ignored — the env var (`pnpm_config_*`, lowercase; pnpm 11 dropped `npm_config_*`) makes OFF explicit and version-proof rather than relying on that default. No `.npmrc` edit (which pnpm ignores) and no CLI flag (pnpm exposes none) can do this. - Drop the now-dead `id-token: write`, reverting the job to `permissions: { contents: read }` — no OIDC token is minted, matching the solid-markdown precedent and avoiding an orphaned permission widening on a token-handling workflow. Spec-impact: none Refs RIG-2187 Co-authored-by: Matt Wilkinson --- .github/workflows/publish-rigelbuild.yml | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-rigelbuild.yml b/.github/workflows/publish-rigelbuild.yml index bed606f57..b3b387b5a 100644 --- a/.github/workflows/publish-rigelbuild.yml +++ b/.github/workflows/publish-rigelbuild.yml @@ -23,12 +23,11 @@ jobs: if: github.repository_owner == 'RigelBuild' runs-on: ubuntu-latest permissions: + # No provenance attestation is generated (see the publish step's + # pnpm_config_provenance=false), so no OIDC token is minted and the job + # needs no id-token:write — read is the whole grant, matching the + # @rigelbuild/solid-markdown precedent. Auth is the NPM_TOKEN secret. contents: read - # The repo root .npmrc sets provenance=true, so `pnpm publish` generates - # an npm provenance attestation, which is signed via GitHub OIDC and needs - # id-token: write (mirrors upstream release.yml). The NPM_TOKEN below still - # authenticates the upload — provenance is an attestation, not the auth. - id-token: write steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -72,3 +71,15 @@ jobs: # is file-free, and can't be redirected to another host since the # registry is baked into the key. pnpm_config_//registry.npmjs.org/:_authToken: ${{ secrets.NPM_TOKEN }} + # Force npm provenance OFF for this publish. The fork's Solid-2 branch + # keeps repository.url -> the upstream TanStack/virtual repo (frozen + # in DL-015: honest for a fork), but provenance attestation requires + # repository.url to match the *publishing* repo (RigelBuild/virtual) + # or the registry rejects the upload (422). We publish from a fork + # with an upstream-pointed manifest and no provenance — same posture + # as the @rigelbuild/solid-markdown precedent. pnpm >=11 ignores a + # `provenance` key in any .npmrc (only auth/network keys are read), so + # the repo-root .npmrc's provenance=true is already inert here; this + # env var (pnpm_config_*, the only surface pnpm reads it from) makes + # OFF explicit and version-proof rather than relying on that default. + pnpm_config_provenance: 'false'