diff --git a/.changeset/require-solid-rc-15.md b/.changeset/require-solid-rc-15.md new file mode 100644 index 00000000..db618fd9 --- /dev/null +++ b/.changeset/require-solid-rc-15.md @@ -0,0 +1,5 @@ +--- +"@solidjs/router": patch +--- + +Require solid-js / @solidjs/web 2.0.0-rc.15. Link and form claims follow rc.15's claim contract: an element's compiled mount claim fires once, after its initial attributes are applied, and later claims come from compiler-owned `href`/`action` writes or from `claimElementTree` sweeps (frame streams, morphs). The router's claim handlers are unchanged. diff --git a/package.json b/package.json index f640db79..3b284320 100644 --- a/package.json +++ b/package.json @@ -48,22 +48,22 @@ "@rollup/plugin-node-resolve": "15.3.0", "@rollup/plugin-terser": "0.4.4", "@solidjs/vite-plugin": "3.0.0-next.47", - "@solidjs/web": "^2.0.0-rc.14", + "@solidjs/web": "^2.0.0-rc.15", "@types/jest": "^29.5.14", "@types/node": "^22.10.0", "babel-preset-solid": "^2.0.0-rc.2", "jsdom": "^25.0.1", "prettier": "^3.4.1", "rollup": "^4.27.4", - "solid-js": "^2.0.0-rc.14", + "solid-js": "^2.0.0-rc.15", "typescript": "^5.7.2", "vite": "^8.2.2", "vitest": "^4.1.11" }, "peerDependencies": { - "@solidjs/web": "^2.0.0-rc.14", + "@solidjs/web": "^2.0.0-rc.15", "filesystem-routing": ">=0.4.0", - "solid-js": "^2.0.0-rc.14" + "solid-js": "^2.0.0-rc.15" }, "peerDependenciesMeta": { "filesystem-routing": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ee270d4..28e1c43c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,10 +32,10 @@ importers: version: 0.4.4(rollup@4.27.4) '@solidjs/vite-plugin': specifier: 3.0.0-next.47 - version: 3.0.0-next.47(@solidjs/web@2.0.0-rc.14(solid-js@2.0.0-rc.14))(solid-js@2.0.0-rc.14)(vite@8.2.2(@types/node@22.10.0)(terser@5.36.0)) + version: 3.0.0-next.47(@solidjs/web@2.0.0-rc.15(solid-js@2.0.0-rc.15))(solid-js@2.0.0-rc.15)(vite@8.2.2(@types/node@22.10.0)(terser@5.36.0)) '@solidjs/web': - specifier: ^2.0.0-rc.14 - version: 2.0.0-rc.14(solid-js@2.0.0-rc.14) + specifier: ^2.0.0-rc.15 + version: 2.0.0-rc.15(solid-js@2.0.0-rc.15) '@types/jest': specifier: ^29.5.14 version: 29.5.14 @@ -44,7 +44,7 @@ importers: version: 22.10.0 babel-preset-solid: specifier: ^2.0.0-rc.2 - version: 2.0.0-rc.2(@babel/core@7.26.0)(solid-js@2.0.0-rc.14) + version: 2.0.0-rc.2(@babel/core@7.26.0)(solid-js@2.0.0-rc.15) jsdom: specifier: ^25.0.1 version: 25.0.1 @@ -55,8 +55,8 @@ importers: specifier: ^4.27.4 version: 4.27.4 solid-js: - specifier: ^2.0.0-rc.14 - version: 2.0.0-rc.14 + specifier: ^2.0.0-rc.15 + version: 2.0.0-rc.15 typescript: specifier: ^5.7.2 version: 5.7.2 @@ -877,8 +877,8 @@ packages: '@solidjs/compiler@2.0.0-rc.14': resolution: {integrity: sha512-eCTVcxXl1UHOisRMYg+hABwiiTkfTmSkaOFtAb8bjzeqpLlju3lo6pT2qWPfUbrvnslbNGnLZd+HZSIVgZu9VQ==} - '@solidjs/signals@2.0.0-rc.14': - resolution: {integrity: sha512-s3v1wpPcX3wfNO0WUERrPrXS+qqJ4ylzH9QTIRKqrgcMVeLEOXgFa3hZrhEoVTp+lVAgido2NH6GCI79twe2fg==} + '@solidjs/signals@2.0.0-rc.15': + resolution: {integrity: sha512-rMhLaYnBvpb9sB3NJGPsqIxjgr0XKU2nvBS7jhWX2fKx+K/m5O0VoAuCgYuTie3BMBZU9vX08NdnFXg508pcew==} engines: {node: '>=22.12.0'} '@solidjs/vite-plugin@3.0.0-next.47': @@ -896,11 +896,11 @@ packages: '@testing-library/jest-dom': optional: true - '@solidjs/web@2.0.0-rc.14': - resolution: {integrity: sha512-MhHqUoVBdgAG7EZOANx7Y5SuCFH7sBxzt9uBSYLpJxE503tqoi7pX1UhV7PWxw4LvmPe1PZkdkSOZ8BLchZkhQ==} + '@solidjs/web@2.0.0-rc.15': + resolution: {integrity: sha512-xwt4rNZ9CXTdMraFqmVn/AXuHIXQJ3jnrKyzEZ4ZFdRCBml0VxL8pA5D3YZHSjHoF7okNAmiVtyRCMNz15jQCg==} engines: {node: '>=22.12.0'} peerDependencies: - solid-js: ^2.0.0-rc.14 + solid-js: ^2.0.0-rc.15 '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1734,8 +1734,8 @@ packages: smob@1.4.1: resolution: {integrity: sha512-9LK+E7Hv5R9u4g4C3p+jjLstaLe11MDsL21UpYaCNmapvMkYhqCV4A/f/3gyH8QjMyh6l68q9xC85vihY9ahMQ==} - solid-js@2.0.0-rc.14: - resolution: {integrity: sha512-fmaqUHgbITf18Hedur6gPnaPE4GrdPgkTbLujHqM5Vl5jxWf9rxwFh6hZMXQs+Kg1pu8KYuOMfqnuSizFQ9I6Q==} + solid-js@2.0.0-rc.15: + resolution: {integrity: sha512-HXiWAEG5Rjp5C7reElUlvdriIadcbpwWwVdztTf0wuxeirw49rGuIzVbSRn06p6GjlINwi5d24Z46b478hNdvw==} engines: {node: '>=22.12.0'} source-map-js@1.2.1: @@ -2904,29 +2904,29 @@ snapshots: '@solidjs/compiler-wasm32-wasi': 2.0.0-rc.14 '@solidjs/compiler-win32-x64-msvc': 2.0.0-rc.14 - '@solidjs/signals@2.0.0-rc.14': {} + '@solidjs/signals@2.0.0-rc.15': {} - '@solidjs/vite-plugin@3.0.0-next.47(@solidjs/web@2.0.0-rc.14(solid-js@2.0.0-rc.14))(solid-js@2.0.0-rc.14)(vite@8.2.2(@types/node@22.10.0)(terser@5.36.0))': + '@solidjs/vite-plugin@3.0.0-next.47(@solidjs/web@2.0.0-rc.15(solid-js@2.0.0-rc.15))(solid-js@2.0.0-rc.15)(vite@8.2.2(@types/node@22.10.0)(terser@5.36.0))': dependencies: '@ampproject/remapping': 2.3.0 '@babel/core': 7.29.7 '@solidjs/babel-plugin': 2.0.0-rc.14(@babel/core@7.29.7) '@solidjs/compiler': 2.0.0-rc.14 - '@solidjs/web': 2.0.0-rc.14(solid-js@2.0.0-rc.14) + '@solidjs/web': 2.0.0-rc.15(solid-js@2.0.0-rc.15) '@types/babel__core': 7.20.5 merge-anything: 5.1.7 - solid-js: 2.0.0-rc.14 + solid-js: 2.0.0-rc.15 vite: 8.2.2(@types/node@22.10.0)(terser@5.36.0) vitefu: 1.0.4(vite@8.2.2(@types/node@22.10.0)(terser@5.36.0)) transitivePeerDependencies: - '@tsrx/core' - supports-color - '@solidjs/web@2.0.0-rc.14(solid-js@2.0.0-rc.14)': + '@solidjs/web@2.0.0-rc.15(solid-js@2.0.0-rc.15)': dependencies: seroval: 1.6.7 seroval-plugins: 1.6.7(seroval@1.6.7) - solid-js: 2.0.0-rc.14 + solid-js: 2.0.0-rc.15 '@standard-schema/spec@1.1.0': {} @@ -3080,12 +3080,12 @@ snapshots: asynckit@0.4.0: {} - babel-preset-solid@2.0.0-rc.2(@babel/core@7.26.0)(solid-js@2.0.0-rc.14): + babel-preset-solid@2.0.0-rc.2(@babel/core@7.26.0)(solid-js@2.0.0-rc.15): dependencies: '@babel/core': 7.26.0 '@dom-expressions/babel-plugin-jsx': 0.50.0-next.44(@babel/core@7.26.0) optionalDependencies: - solid-js: 2.0.0-rc.14 + solid-js: 2.0.0-rc.15 better-path-resolve@1.0.0: dependencies: @@ -3746,9 +3746,9 @@ snapshots: smob@1.4.1: {} - solid-js@2.0.0-rc.14: + solid-js@2.0.0-rc.15: dependencies: - '@solidjs/signals': 2.0.0-rc.14 + '@solidjs/signals': 2.0.0-rc.15 csstype: 3.2.3 seroval: 1.6.7 seroval-plugins: 1.6.7(seroval@1.6.7) diff --git a/test/action-commit-release.spec.tsx b/test/action-commit-release.spec.tsx index 57b478e3..80014293 100644 --- a/test/action-commit-release.spec.tsx +++ b/test/action-commit-release.spec.tsx @@ -11,7 +11,7 @@ */ import { action, createMemo, createRoot, createSignal, For, Loading, Show } from "solid-js"; import { vi } from "vitest"; -import { claimElement, redirect, render } from "@solidjs/web"; +import { claimElementTree, redirect, render } from "@solidjs/web"; import { action as routerAction, createRouter, @@ -199,14 +199,14 @@ describe("#649 router actions release at the transition's commit", () => { // a server-component morph resets attributes to the server HTML (which // never carries aria-busy), then re-claims the element t.form().removeAttribute("aria-busy"); - claimElement(t.form()); + claimElementTree(t.form()); expect(t.busy()).toBe("true"); t.mutation().resolve("ok"); await until(() => t.log.length > 0); expect(t.busy()).toBeNull(); - claimElement(t.form()); + claimElementTree(t.form()); expect(t.busy()).toBeNull(); t.dispose(); }); @@ -315,7 +315,7 @@ describe("#649 router actions release at the transition's commit", () => { t.form().requestSubmit(); await t.mutationStarted(); expect(t.busy()).toBe("false"); - claimElement(t.form()); + claimElementTree(t.form()); expect(t.busy()).toBe("false"); t.mutation().resolve("ok"); @@ -419,7 +419,7 @@ describe("#649 router actions release at the transition's commit", () => { expect(t.busy()).toBe("true"); t.form().setAttribute("aria-busy", "false"); - claimElement(t.form()); + claimElementTree(t.form()); expect(t.busy()).toBe("false"); t.mutation().resolve("ok"); diff --git a/test/claims.spec.tsx b/test/claims.spec.tsx index 32227505..9e5a8467 100644 --- a/test/claims.spec.tsx +++ b/test/claims.spec.tsx @@ -4,7 +4,7 @@ * wrapper component. These specs compile real JSX, so they exercise the full * chain — compiler claim emission → runtime claim hook → router consumer. */ -import { claimElement, render } from "@solidjs/web"; +import { claimElementTree, render } from "@solidjs/web"; import { createRoot, createSignal, @@ -351,7 +351,7 @@ describe("compiler-claimed anchors", () => { about.removeAttribute("aria-current"); about.removeAttribute("data-active"); - claimElement(about); + claimElementTree(about); expect(about.getAttribute("aria-current")).toBe("page"); expect(about.hasAttribute("data-active")).toBe(true); @@ -725,7 +725,7 @@ describe("compiler-claimed anchors", () => { // state stripped) and re-claims step.setAttribute("aria-current", "step"); step.removeAttribute("data-active"); - claimElement(step); + claimElementTree(step); expect(step.getAttribute("aria-current")).toBe("step"); expect(step.hasAttribute("data-active")).toBe(true); @@ -807,7 +807,7 @@ describe("compiler-claimed anchors", () => { const morph = (a: Element) => { a.removeAttribute("aria-current"); a.removeAttribute("data-active"); - claimElement(a); + claimElementTree(a); }; try { const about = div.querySelector('[data-testid="about"]')!; diff --git a/test/data/events.spec.ts b/test/data/events.spec.ts index a56db53b..7355b377 100644 --- a/test/data/events.spec.ts +++ b/test/data/events.spec.ts @@ -1,4 +1,4 @@ -import { claimElement } from "@solidjs/web"; +import { claimElement, setAttribute } from "@solidjs/web"; import { createRoot, createSignal } from "solid-js"; import { vi } from "vitest"; import { setRouterFormHandler, setupNativeEvents } from "../../src/data/events.js"; @@ -959,10 +959,10 @@ describe("tap and ambient preload strategies", () => { const a = link({ href: "/a", preload: "viewport" }); observer().intersect(a); vi.runAllTimers(); - a.setAttribute("href", "/b"); - // the re-claim runs under the effect writing `href`, which outlives - // neither the anchor nor its arming - createRoot(d => (claimElement(a), d))(); + // a compiler-owned `href` write re-claims the claimed anchor; it runs + // under the effect writing `href`, which outlives neither the anchor + // nor its arming + createRoot(d => (setAttribute(a, "href", "/b"), d))(); observer().intersect(a); vi.runAllTimers(); expect(preloaded()).toEqual(["/a:false", "/b:false"]); diff --git a/test/hash-history.spec.tsx b/test/hash-history.spec.tsx index 56e63bf7..3d24764d 100644 --- a/test/hash-history.spec.tsx +++ b/test/hash-history.spec.tsx @@ -92,7 +92,7 @@ describe("hashHistory navigation (#582)", () => { // header carries (display today, logical once @solidjs/web unwraps the // brand) must route — this is the client leg of an action redirect expect(isHref(paths.page1)).toBe(true); - const res = redirect(paths.page1); + const res = redirect(paths.page1); const target = res.headers.get("Location")!; nav(target, { scroll: false }); await settle(); diff --git a/test/navigation-pending.spec.tsx b/test/navigation-pending.spec.tsx index 4f3d737d..bc7e7b0f 100644 --- a/test/navigation-pending.spec.tsx +++ b/test/navigation-pending.spec.tsx @@ -429,10 +429,10 @@ describe("navigation pending state (characterization, #655)", () => { "| navigate /slow/2: call routing=true target=/slow/1 intent=navigate at=/", "fetch:2:navigate", "routing:true@/slow/2", - "link:/slow/1:false", - "link:/slow/2:true", "attr:/slow/1:-", "attr:/slow/2:+", + "link:/slow/1:false", + "link:/slow/2:true", "| navigate /slow/2: flush routing=true target=/slow/2 intent=navigate at=/", "| navigate /slow/2: settled routing=true target=/slow/2 intent=navigate at=/", "| resolved 1 routing=true target=/slow/2 intent=navigate at=/", diff --git a/test/server/query-response-cookies.spec.ts b/test/server/query-response-cookies.spec.ts index efe9516b..4ecf2138 100644 --- a/test/server/query-response-cookies.spec.ts +++ b/test/server/query-response-cookies.spec.ts @@ -8,8 +8,9 @@ describe.each(["returned", "thrown"] as const)("%s query response cookies", outc "%s appends cookies once across concurrent and cached reads", async kind => { const value = { message: "query result" }; - const result = kind === "respond" ? respond(value) : new Response("query body"); - const response = result instanceof Response ? result : result.response!; + // respond() is typed as its value; at runtime it is a Response envelope + const result = (kind === "respond" ? respond(value) : new Response("query body")) as Response; + const response = result; response.headers.set("Cache-Control", "private, no-store"); response.headers.set("X-Query", "metadata"); const cookies = [ diff --git a/test/server/query-response-headers.spec.tsx b/test/server/query-response-headers.spec.tsx index 46b07630..22aa9679 100644 --- a/test/server/query-response-headers.spec.tsx +++ b/test/server/query-response-headers.spec.tsx @@ -19,8 +19,11 @@ describe.each(["returned", "thrown"] as const)( "%s preserves document headers, cookies and query metadata", async kind => { const value = { message: "query result" }; - const result = kind === "respond" ? respond(value) : new Response("query body"); - const response = result instanceof Response ? result : result.response!; + // respond() is typed as its value; at runtime it is a Response envelope + const result = ( + kind === "respond" ? respond(value) : new Response("query body") + ) as Response; + const response = result; response.headers.set("Content-Type", "application/json"); response.headers.set("Content-Length", "42"); response.headers.set("Content-Encoding", "br");