From eb8e7777417dc1bbce2595309b7d5735913d2be3 Mon Sep 17 00:00:00 2001 From: Maurice <61554723+mauriceboe@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:34:18 +0200 Subject: [PATCH] chore(client): move zustand to 5.0.14 (#1825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last React-18 leftover in the dependency tree. v4 declared use-sync-external-store as a hard dependency and its main entry imported the shim; v5 declares no dependencies at all and calls React.useSyncExternalStore directly. Only zustand/traditional still reaches for the shim, and nothing here imports it, so it no longer reaches the bundle. The upgrade itself needed one line. What it needed more of was checking, because the one thing v5 changes about behaviour does not show up as a test failure. v4 subscribed through useSyncExternalStoreWithSelector, which memoised the selector's result. v5 hands React a plain getSnapshot of `() => selector(api.getState())` and compares with Object.is. A selector that builds a fresh object or array therefore returns a new reference on every call, and React reads that as "the store changed" — an infinite render loop, or at best a re-render on every parent render. All 294 store selectors return a property or a primitive. None builds an object literal, array literal, filter/map/slice result, `?? []` default, Object.keys or a new Set/Map, so none of them needed useShallow. The suite confirms it: 533 files, 11,165 tests, and zero "getSnapshot should be cached" or "Maximum update depth" warnings anywhere in the output. The one source change is the seedStore test helper, which was typed against a hand-written `{ setState: (partial, replace?: boolean) => void }` shape. As of v5 setState is an overload pair — partial with replace?: false, full state with replace: true — and no single signature is assignable to that. Typing it against zustand's own StoreApi fixes all 129 resulting errors and is what the helper meant in the first place. --- client/package.json | 2 +- client/tests/helpers/store.ts | 8 +- package-lock.json | 172 +++++++++++++++------------------- 3 files changed, 86 insertions(+), 96 deletions(-) diff --git a/client/package.json b/client/package.json index e1938a4bb..f406faf07 100644 --- a/client/package.json +++ b/client/package.json @@ -58,7 +58,7 @@ "topojson-client": "^3.1.0", "tz-lookup": "^6.1.25", "zod": "^4.3.6", - "zustand": "^4.5.2" + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/client/tests/helpers/store.ts b/client/tests/helpers/store.ts index 9df8a5dd2..e7c8030d2 100644 --- a/client/tests/helpers/store.ts +++ b/client/tests/helpers/store.ts @@ -1,3 +1,4 @@ +import type { StoreApi } from 'zustand'; import { useAuthStore } from '../../src/store/authStore'; import { useTripStore } from '../../src/store/tripStore'; import { useSettingsStore } from '../../src/store/settingsStore'; @@ -30,11 +31,16 @@ export function resetAllStores(): void { * including partial nested objects (e.g. only `settings.time_format`). The * store's own setState wants the exact field types, so seeding accepts a * deep-partial view and casts at the boundary. + * + * Typed against zustand's own StoreApi rather than a hand-written + * `{ setState: ... }` shape: as of v5 setState is an overload pair (partial + + * replace?: false, full state + replace: true), and no single signature is + * assignable to it. */ type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T; export function seedStore( - store: { setState: (partial: Partial, replace?: boolean) => void }, + store: Pick, 'setState'>, state: DeepPartial, ): void { store.setState(state as Partial); diff --git a/package-lock.json b/package-lock.json index 5c162dc42..02909331f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "topojson-client": "^3.1.0", "tz-lookup": "^6.1.25", "zod": "^4.3.6", - "zustand": "^4.5.2" + "zustand": "^5.0.14" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -227,6 +227,35 @@ } } }, + "client/node_modules/zustand": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.14.tgz", + "integrity": "sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "nest-mcp": { "name": "@trek/nest-mcp", "version": "3.4.1", @@ -3118,9 +3147,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3138,9 +3164,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3158,9 +3181,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3178,9 +3198,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3198,9 +3215,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3218,9 +3232,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -3270,9 +3281,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3296,9 +3304,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3322,9 +3327,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3348,9 +3350,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3374,9 +3373,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3400,9 +3396,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3425,9 +3418,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -3450,9 +3440,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -6269,7 +6256,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.62.0", @@ -6283,7 +6271,8 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.62.0", @@ -6297,7 +6286,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.62.0", @@ -6311,7 +6301,8 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.62.0", @@ -6325,7 +6316,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.62.0", @@ -6339,7 +6331,8 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.62.0", @@ -6353,7 +6346,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.62.0", @@ -6367,7 +6361,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.62.0", @@ -6381,7 +6376,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.62.0", @@ -6408,7 +6404,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-loong64-musl": { "version": "4.62.0", @@ -6422,7 +6419,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.62.0", @@ -6436,7 +6434,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-musl": { "version": "4.62.0", @@ -6450,7 +6449,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.62.0", @@ -6464,7 +6464,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.62.0", @@ -6478,7 +6479,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.62.0", @@ -6492,7 +6494,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.62.0", @@ -6506,7 +6509,8 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.62.0", @@ -6533,7 +6537,8 @@ "optional": true, "os": [ "openbsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.62.0", @@ -6547,7 +6552,8 @@ "optional": true, "os": [ "openharmony" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.62.0", @@ -6561,7 +6567,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.62.0", @@ -6575,7 +6582,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.62.0", @@ -6589,7 +6597,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.62.0", @@ -6603,7 +6612,8 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@scarf/scarf": { "version": "1.4.0", @@ -19776,6 +19786,8 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", + "optional": true, + "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -21125,34 +21137,6 @@ "zod": "^3.25.0 || ^4.0.0" } }, - "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",