From d9d584e556327e91ee44bdcf0bd5d48c4c7547aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:03:36 +0000 Subject: [PATCH 1/4] Initial plan From ee01222e72464b4f4e4fff31fa63516d07772855 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:12:29 +0000 Subject: [PATCH 2/4] Create shared DebugInfo component for all demos Co-authored-by: JasonMore <383719+JasonMore@users.noreply.github.com> --- src/Layout.tsx | 6 ++- src/components/DebugInfo.tsx | 47 +++++++++++++++++++ .../propDrilling/PropDrillingRenderDemo.tsx | 4 +- .../demoControls/PropDrillingDebugInfo.tsx | 32 ------------- .../PropDrillingNaiveRenderDemo.tsx | 4 +- .../demoControls/PropDrillingDebugInfo.tsx | 32 ------------- src/examples/zustand-query/ZustandQuery.tsx | 17 ++++++- .../zustand-query/demoControls/DebugInfo.tsx | 21 --------- src/examples/zustand/ZustandRenderDemo.tsx | 11 ++++- .../zustand/demoControls/DebugInfo.tsx | 17 ------- 10 files changed, 79 insertions(+), 112 deletions(-) create mode 100644 src/components/DebugInfo.tsx delete mode 100644 src/examples/propDrilling/demoControls/PropDrillingDebugInfo.tsx delete mode 100644 src/examples/propDrillingNaive/demoControls/PropDrillingDebugInfo.tsx delete mode 100644 src/examples/zustand-query/demoControls/DebugInfo.tsx delete mode 100644 src/examples/zustand/demoControls/DebugInfo.tsx diff --git a/src/Layout.tsx b/src/Layout.tsx index feae79e..1dfe562 100644 --- a/src/Layout.tsx +++ b/src/Layout.tsx @@ -12,7 +12,9 @@ export const Layout = () => { resetTokenCounter(); // Update page title based on current route - const currentRoute = routes.find((route) => route.path === location.pathname); + const currentRoute = routes.find( + (route) => route.path === location.pathname, + ); if (currentRoute) { document.title = currentRoute.title; } else if (location.pathname === homeRoute.path) { @@ -26,4 +28,4 @@ export const Layout = () => { ); -}; \ No newline at end of file +}; diff --git a/src/components/DebugInfo.tsx b/src/components/DebugInfo.tsx new file mode 100644 index 0000000..2c6d038 --- /dev/null +++ b/src/components/DebugInfo.tsx @@ -0,0 +1,47 @@ +import { memo } from "react"; +import css from "./css/DebugInfo.module.css"; +import sharedStyles from "./css/shared.module.css"; +import type { World } from "../types/World.ts"; + +type Snapshot = { + selectedWorldId: string; + hello: { worlds: World[] | undefined }; +}; + +type Props = { + /** + * Snapshot data to display. + */ + snapshot: Snapshot | null; + /** + * Optional title for the debug panel. Defaults to "Store snapshot". + */ + title?: string; +}; + +export const DebugInfo = memo(({ snapshot, title }: Props) => { + const displayTitle = title || "Store snapshot"; + + const pretty = + snapshot !== null && snapshot !== undefined + ? JSON.stringify( + { + selectedWorldId: snapshot.selectedWorldId, + hello: { + worlds: snapshot.hello.worlds?.map((world) => ({ ...world })), + }, + }, + null, + 2, + ) + : "Snapshot pending…"; + + return ( +
+
{displayTitle}
+
{pretty}
+
+ ); +}); + +DebugInfo.displayName = "DebugInfo"; diff --git a/src/examples/propDrilling/PropDrillingRenderDemo.tsx b/src/examples/propDrilling/PropDrillingRenderDemo.tsx index e658016..794f9dd 100644 --- a/src/examples/propDrilling/PropDrillingRenderDemo.tsx +++ b/src/examples/propDrilling/PropDrillingRenderDemo.tsx @@ -4,7 +4,7 @@ import css from "../../components/css/DemoLayout.module.css"; import { RenderToken } from "../../components/perf/RenderToken.tsx"; import type { World } from "../../types/World.ts"; import { WorldApp } from "./exampleComponents/WorldApp.tsx"; -import { PropDrillingDebugInfo } from "./demoControls/PropDrillingDebugInfo.tsx"; +import { DebugInfo } from "../../components/DebugInfo.tsx"; import type { WorldsResponse } from "../../api/worlds.ts"; type Snapshot = { @@ -47,7 +47,7 @@ export function PropDrillingRenderDemo() { onSnapshotChange={setSnapshot} revalidate={revalidator.revalidate} /> - + ); } diff --git a/src/examples/propDrilling/demoControls/PropDrillingDebugInfo.tsx b/src/examples/propDrilling/demoControls/PropDrillingDebugInfo.tsx deleted file mode 100644 index e98a8fe..0000000 --- a/src/examples/propDrilling/demoControls/PropDrillingDebugInfo.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import css from "../../../components/css/DebugInfo.module.css"; -import sharedStyles from "../../../components/css/shared.module.css"; -import type { World } from "../../zustand/data/types.ts"; - -type Props = { - snapshot: { - selectedWorldId: string; - hello: { worlds: World[] }; - } | null; -}; - -export function PropDrillingDebugInfo({ snapshot }: Props) { - const pretty = snapshot - ? JSON.stringify( - { - selectedWorldId: snapshot.selectedWorldId, - hello: { - worlds: snapshot.hello.worlds.map((world) => ({ ...world })), - }, - }, - null, - 2, - ) - : "Snapshot pending…"; - - return ( -
-
Top level state snapshot
-
{pretty}
-
- ); -} diff --git a/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx b/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx index 5103056..7430b6a 100644 --- a/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx +++ b/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx @@ -3,7 +3,7 @@ import css from "../../components/css/DemoLayout.module.css"; import { RenderToken } from "../../components/perf/RenderToken.tsx"; import type { World } from "../../types/World.ts"; import { PropDrillingWorldApp } from "./exampleComponents/WorldApp.tsx"; -import { PropDrillingDebugInfo } from "./demoControls/PropDrillingDebugInfo.tsx"; +import { DebugInfo } from "../../components/DebugInfo.tsx"; import { fetchWorlds } from "../../api/worlds.ts"; type Snapshot = { @@ -50,7 +50,7 @@ export function PropDrillingNaiveRenderDemo() { setWorlds={setWorlds} onSnapshotChange={setSnapshot} /> - + ); } diff --git a/src/examples/propDrillingNaive/demoControls/PropDrillingDebugInfo.tsx b/src/examples/propDrillingNaive/demoControls/PropDrillingDebugInfo.tsx deleted file mode 100644 index e98a8fe..0000000 --- a/src/examples/propDrillingNaive/demoControls/PropDrillingDebugInfo.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import css from "../../../components/css/DebugInfo.module.css"; -import sharedStyles from "../../../components/css/shared.module.css"; -import type { World } from "../../zustand/data/types.ts"; - -type Props = { - snapshot: { - selectedWorldId: string; - hello: { worlds: World[] }; - } | null; -}; - -export function PropDrillingDebugInfo({ snapshot }: Props) { - const pretty = snapshot - ? JSON.stringify( - { - selectedWorldId: snapshot.selectedWorldId, - hello: { - worlds: snapshot.hello.worlds.map((world) => ({ ...world })), - }, - }, - null, - 2, - ) - : "Snapshot pending…"; - - return ( -
-
Top level state snapshot
-
{pretty}
-
- ); -} diff --git a/src/examples/zustand-query/ZustandQuery.tsx b/src/examples/zustand-query/ZustandQuery.tsx index bf36f18..03282db 100644 --- a/src/examples/zustand-query/ZustandQuery.tsx +++ b/src/examples/zustand-query/ZustandQuery.tsx @@ -1,9 +1,22 @@ import { QueryClientProvider } from "@tanstack/react-query"; import css from "../../components/css/DemoLayout.module.css"; -import { DebugInfo } from "./demoControls/DebugInfo"; +import { DebugInfo } from "../../components/DebugInfo.tsx"; import { WorldApp } from "./components/WorldApp"; import { memo } from "react"; import { queryClient } from "./data/WorldData.ts"; +import { useGetWorlds } from "./data/WorldData.ts"; +import { useWorldStore } from "../zustand/data/WorldStore.tsx"; + +function ZustandQueryDebugInfo() { + const selectedWorldId = useWorldStore((s) => s.selectedWorldId); + const { data } = useGetWorlds(); + + return ( + + ); +} export const ZustandQuery = memo(() => ( @@ -25,7 +38,7 @@ export const ZustandQuery = memo(() => (

- +
)); diff --git a/src/examples/zustand-query/demoControls/DebugInfo.tsx b/src/examples/zustand-query/demoControls/DebugInfo.tsx deleted file mode 100644 index aca91ab..0000000 --- a/src/examples/zustand-query/demoControls/DebugInfo.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { memo } from "react"; -import css from "../../../components/css/DebugInfo.module.css"; -import sharedStyles from "../../../components/css/shared.module.css"; -import { useGetWorlds } from "../data/WorldData.ts"; -import { useWorldStore } from "../../zustand/data/WorldStore.tsx"; - -export const DebugInfo = memo(() => { - const selectedWorldId = useWorldStore((s) => s.selectedWorldId); - const { data } = useGetWorlds(); - - const snapshot = { selectedWorldId, hello: { worlds: data?.worlds } }; - - return ( -
-
Store snapshot
-
{JSON.stringify(snapshot, null, 2)}
-
- ); -}); - -DebugInfo.displayName = "DebugInfo"; diff --git a/src/examples/zustand/ZustandRenderDemo.tsx b/src/examples/zustand/ZustandRenderDemo.tsx index d392c07..644a86c 100644 --- a/src/examples/zustand/ZustandRenderDemo.tsx +++ b/src/examples/zustand/ZustandRenderDemo.tsx @@ -1,12 +1,19 @@ import { useEffect } from "react"; import { useLoaderData, useRevalidator } from "react-router-dom"; import css from "../../components/css/DemoLayout.module.css"; -import { DebugInfo } from "./demoControls/DebugInfo.tsx"; +import { DebugInfo } from "../../components/DebugInfo.tsx"; import { RenderToken } from "../../components/perf/RenderToken.tsx"; import { WorldApp } from "./exampleComponents/WorldApp.tsx"; import { useWorldStore } from "./data/WorldStore.tsx"; import type { WorldsResponse } from "../../api/worlds.ts"; +function ZustandDebugInfo() { + const selectedWorldId = useWorldStore((s) => s.selectedWorldId); + const worlds = useWorldStore((s) => s.hello.worlds); + + return ; +} + export function ZustandRenderDemo() { const data = useLoaderData() as WorldsResponse; const revalidator = useRevalidator(); @@ -37,7 +44,7 @@ export function ZustandRenderDemo() {

- + ); } diff --git a/src/examples/zustand/demoControls/DebugInfo.tsx b/src/examples/zustand/demoControls/DebugInfo.tsx deleted file mode 100644 index 20fd87d..0000000 --- a/src/examples/zustand/demoControls/DebugInfo.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import css from "../../../components/css/DebugInfo.module.css"; -import sharedStyles from "../../../components/css/shared.module.css"; -import { useWorldStore } from "../data/WorldStore.tsx"; - -export function DebugInfo() { - const selectedWorldId = useWorldStore((s) => s.selectedWorldId); - const worlds = useWorldStore((s) => s.hello.worlds); - - const snapshot = { selectedWorldId, hello: { worlds } }; - - return ( -
-
Store snapshot
-
{JSON.stringify(snapshot, null, 2)}
-
- ); -} From b321cc5840e7470916dc9d47da4a1a87aabadf34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:13:54 +0000 Subject: [PATCH 3/4] Simplify null check in DebugInfo component Co-authored-by: JasonMore <383719+JasonMore@users.noreply.github.com> --- src/components/DebugInfo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DebugInfo.tsx b/src/components/DebugInfo.tsx index 2c6d038..3fb1f7c 100644 --- a/src/components/DebugInfo.tsx +++ b/src/components/DebugInfo.tsx @@ -23,7 +23,7 @@ export const DebugInfo = memo(({ snapshot, title }: Props) => { const displayTitle = title || "Store snapshot"; const pretty = - snapshot !== null && snapshot !== undefined + snapshot != null ? JSON.stringify( { selectedWorldId: snapshot.selectedWorldId, From 19796520600d99cb8d8c0fba2485d8b8d44785d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Oct 2025 16:57:41 +0000 Subject: [PATCH 4/4] Merge main branch and resolve conflicts Co-authored-by: JasonMore <383719+JasonMore@users.noreply.github.com> --- README.md | 2 +- package-lock.json | 636 ++++++++++-------- package.json | 26 +- src/Home.tsx | 20 +- src/api/worlds.ts | 10 +- src/components/DebugInfo.tsx | 2 +- .../propDrilling/PropDrillingRenderDemo.tsx | 23 +- .../propDrilling/data/solarSystemWorlds.ts | 2 +- src/examples/propDrilling/data/types.ts | 8 - .../exampleComponents/WorldApp.tsx | 11 +- .../worldSelector/WorldSelector.tsx | 2 +- .../exampleComponents/worldsViewer/World.tsx | 2 +- .../worldsViewer/WorldInfo.tsx | 2 +- .../worldsViewer/WorldList.tsx | 2 +- .../worldsViewer/WorldsViewer.tsx | 2 +- .../PropDrillingNaiveRenderDemo.tsx | 22 +- .../data/solarSystemWorlds.ts | 2 +- src/examples/propDrillingNaive/data/types.ts | 8 - .../exampleComponents/WorldApp.tsx | 3 +- src/examples/zustand-query/ZustandQuery.tsx | 16 +- .../zustand-query/data/solarSystemWorlds.ts | 2 +- src/examples/zustand-query/data/types.ts | 8 - src/examples/zustand/ZustandRenderDemo.tsx | 13 +- src/examples/zustand/data/WorldStore.tsx | 3 +- .../zustand/data/solarSystemWorlds.ts | 2 +- src/examples/zustand/data/types.ts | 8 - src/routes.ts | 8 +- src/types/World.ts | 13 - 28 files changed, 438 insertions(+), 420 deletions(-) delete mode 100644 src/examples/propDrilling/data/types.ts delete mode 100644 src/examples/propDrillingNaive/data/types.ts delete mode 100644 src/examples/zustand-query/data/types.ts delete mode 100644 src/examples/zustand/data/types.ts delete mode 100644 src/types/World.ts diff --git a/README.md b/README.md index f270cfa..9e9cb6c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# React Performance Examples +# React Performance Examples ([Live Demo](https://stackblitz.com/~/github.com/JasonMore/react-performance-examples)) Hands-on demos that contrast common React state patterns with their performance-minded counterparts. Each example surfaces rerender behavior through `RenderToken` counters. diff --git a/package-lock.json b/package-lock.json index a66fad3..9259a33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,25 +9,25 @@ "version": "0.0.0", "dependencies": { "@tanstack/react-query": "^5.90.2", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^7.9.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.3", "react-scan": "^0.4.3", "zustand": "^5.0.8" }, "devDependencies": { - "@eslint/js": "^9.36.0", - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.3", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", + "@eslint/js": "^9.37.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.37.0", + "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-refresh": "^0.4.23", "globals": "^16.4.0", "prettier": "^3.6.2", - "typescript": "~5.8.3", - "typescript-eslint": "^8.44.0", - "vite": "^7.1.7" + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.9" } }, "node_modules/@babel/code-frame": { @@ -742,19 +742,24 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.16.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -798,10 +803,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -819,12 +825,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" }, "engines": { @@ -924,6 +931,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -937,6 +945,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -946,6 +955,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -991,10 +1001,11 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.35", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz", - "integrity": "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==", - "dev": true + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", + "dev": true, + "license": "MIT" }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", @@ -1386,7 +1397,8 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "20.19.18", @@ -1398,20 +1410,22 @@ } }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", + "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", + "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", - "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==", "dev": true, + "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-reconciler": { @@ -1424,16 +1438,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", - "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/type-utils": "8.44.0", - "@typescript-eslint/utils": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -1447,7 +1462,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.44.0", + "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -1457,20 +1472,22 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", - "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "engines": { @@ -1486,13 +1503,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", - "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.0", - "@typescript-eslint/types": "^8.44.0", + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" }, "engines": { @@ -1507,13 +1525,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", - "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0" + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1524,10 +1543,11 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", - "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1540,14 +1560,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", - "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -1564,10 +1585,11 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", - "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1577,15 +1599,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", - "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.44.0", - "@typescript-eslint/tsconfig-utils": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1609,6 +1632,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -1618,6 +1642,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1633,6 +1658,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -1641,15 +1667,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", - "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0" + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1664,12 +1691,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", - "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -1681,15 +1709,16 @@ } }, "node_modules/@vitejs/plugin-react": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.3.tgz", - "integrity": "sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", + "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.35", + "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -1799,6 +1828,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -2027,19 +2057,20 @@ } }, "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -2087,22 +2118,30 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.1.tgz", + "integrity": "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", - "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz", + "integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==", "dev": true, + "license": "MIT", "peerDependencies": { "eslint": ">=8.40" } @@ -2214,6 +2253,7 @@ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -2230,6 +2270,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2254,6 +2295,7 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -2292,6 +2334,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2395,7 +2438,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -2466,6 +2510,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2598,6 +2643,7 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -2607,6 +2653,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -2911,25 +2958,28 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "license": "MIT", "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.2.0" } }, "node_modules/react-refresh": { @@ -2942,9 +2992,9 @@ } }, "node_modules/react-router": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz", - "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", + "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -2964,12 +3014,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz", - "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.3.tgz", + "integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==", "license": "MIT", "dependencies": { - "react-router": "7.9.1" + "react-router": "7.9.3" }, "engines": { "node": ">=20.0.0" @@ -3055,6 +3105,7 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -3120,14 +3171,16 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -3224,6 +3277,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3236,6 +3290,7 @@ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=18.12" }, @@ -3275,10 +3330,11 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3288,15 +3344,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz", - "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.44.0", - "@typescript-eslint/parser": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0" + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3369,10 +3426,11 @@ } }, "node_modules/vite": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", - "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", + "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3490,6 +3548,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "node_modules/zustand": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", @@ -3905,15 +3986,18 @@ } }, "@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", - "dev": true + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.0.tgz", + "integrity": "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==", + "dev": true, + "requires": { + "@eslint/core": "^0.16.0" + } }, "@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.16.0.tgz", + "integrity": "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" @@ -3945,9 +4029,9 @@ } }, "@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.37.0.tgz", + "integrity": "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==", "dev": true }, "@eslint/object-schema": { @@ -3957,12 +4041,12 @@ "dev": true }, "@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.0.tgz", + "integrity": "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==", "dev": true, "requires": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, @@ -4077,9 +4161,9 @@ "integrity": "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==" }, "@rolldown/pluginutils": { - "version": "1.0.0-beta.35", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.35.tgz", - "integrity": "sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==", + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", "dev": true }, "@rollup/pluginutils": { @@ -4327,17 +4411,17 @@ } }, "@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", + "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", "requires": { "csstype": "^3.0.2" } }, "@types/react-dom": { - "version": "19.1.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", - "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==", "dev": true, "requires": {} }, @@ -4348,16 +4432,16 @@ "requires": {} }, "@typescript-eslint/eslint-plugin": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", - "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.45.0.tgz", + "integrity": "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/type-utils": "8.44.0", - "@typescript-eslint/utils": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/type-utils": "8.45.0", + "@typescript-eslint/utils": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -4373,75 +4457,75 @@ } }, "@typescript-eslint/parser": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", - "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.45.0.tgz", + "integrity": "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" } }, "@typescript-eslint/project-service": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", - "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.45.0.tgz", + "integrity": "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==", "dev": true, "requires": { - "@typescript-eslint/tsconfig-utils": "^8.44.0", - "@typescript-eslint/types": "^8.44.0", + "@typescript-eslint/tsconfig-utils": "^8.45.0", + "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", - "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.45.0.tgz", + "integrity": "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==", "dev": true, "requires": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0" + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", - "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.45.0.tgz", + "integrity": "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==", "dev": true, "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", - "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.45.0.tgz", + "integrity": "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==", "dev": true, "requires": { - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" } }, "@typescript-eslint/types": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", - "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.45.0.tgz", + "integrity": "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", - "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.45.0.tgz", + "integrity": "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==", "dev": true, "requires": { - "@typescript-eslint/project-service": "8.44.0", - "@typescript-eslint/tsconfig-utils": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/visitor-keys": "8.44.0", + "@typescript-eslint/project-service": "8.45.0", + "@typescript-eslint/tsconfig-utils": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -4477,37 +4561,37 @@ } }, "@typescript-eslint/utils": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", - "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.45.0.tgz", + "integrity": "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.0", - "@typescript-eslint/types": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0" + "@typescript-eslint/scope-manager": "8.45.0", + "@typescript-eslint/types": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0" } }, "@typescript-eslint/visitor-keys": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", - "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.45.0.tgz", + "integrity": "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==", "dev": true, "requires": { - "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" } }, "@vitejs/plugin-react": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.3.tgz", - "integrity": "sha512-PFVHhosKkofGH0Yzrw1BipSedTH68BFF8ZWy1kfUpCtJcouXXY0+racG8sExw7hw0HoX36813ga5o3LTWZ4FUg==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", + "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", "dev": true, "requires": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.35", + "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" } @@ -4734,19 +4818,19 @@ "dev": true }, "eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "version": "9.37.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", + "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-helpers": "^0.4.0", + "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.37.0", + "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4777,16 +4861,21 @@ } }, "eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.1.tgz", + "integrity": "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==", "dev": true, - "requires": {} + "requires": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" + } }, "eslint-plugin-react-refresh": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", - "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.23.tgz", + "integrity": "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA==", "dev": true, "requires": {} }, @@ -5327,16 +5416,16 @@ "dev": true }, "react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==" + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==" }, "react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "requires": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" } }, "react-refresh": { @@ -5346,20 +5435,20 @@ "dev": true }, "react-router": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.1.tgz", - "integrity": "sha512-pfAByjcTpX55mqSDGwGnY9vDCpxqBLASg0BMNAuMmpSGESo/TaOUG6BllhAtAkCGx8Rnohik/XtaqiYUJtgW2g==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.3.tgz", + "integrity": "sha512-4o2iWCFIwhI/eYAIL43+cjORXYn/aRQPgtFRRZb3VzoyQ5Uej0Bmqj7437L97N9NJW4wnicSwLOLS+yCXfAPgg==", "requires": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" } }, "react-router-dom": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.1.tgz", - "integrity": "sha512-U9WBQssBE9B1vmRjo9qTM7YRzfZ3lUxESIZnsf4VjR/lXYz9MHjvOxHzr/aUm4efpktbVOrF09rL/y4VHa8RMw==", + "version": "7.9.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.3.tgz", + "integrity": "sha512-1QSbA0TGGFKTAc/aWjpfW/zoEukYfU4dc1dLkT/vvf54JoGMkW+fNA+3oyo2gWVW1GM7BxjJVHz5GnPJv40rvg==", "requires": { - "react-router": "7.9.1" + "react-router": "7.9.3" } }, "react-scan": { @@ -5446,9 +5535,9 @@ } }, "scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, "semver": { "version": "6.3.1", @@ -5547,21 +5636,21 @@ } }, "typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true }, "typescript-eslint": { - "version": "8.44.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz", - "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==", + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.45.0.tgz", + "integrity": "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==", "dev": true, "requires": { - "@typescript-eslint/eslint-plugin": "8.44.0", - "@typescript-eslint/parser": "8.44.0", - "@typescript-eslint/typescript-estree": "8.44.0", - "@typescript-eslint/utils": "8.44.0" + "@typescript-eslint/eslint-plugin": "8.45.0", + "@typescript-eslint/parser": "8.45.0", + "@typescript-eslint/typescript-estree": "8.45.0", + "@typescript-eslint/utils": "8.45.0" } }, "undici-types": { @@ -5598,9 +5687,9 @@ } }, "vite": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", - "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "version": "7.1.9", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.9.tgz", + "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "requires": { "esbuild": "^0.25.0", @@ -5644,6 +5733,19 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "dev": true + }, + "zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "requires": {} + }, "zustand": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", diff --git a/package.json b/package.json index 46ad7db..ec92a4d 100644 --- a/package.json +++ b/package.json @@ -13,24 +13,24 @@ }, "dependencies": { "@tanstack/react-query": "^5.90.2", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-router-dom": "^7.9.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-router-dom": "^7.9.3", "react-scan": "^0.4.3", "zustand": "^5.0.8" }, "devDependencies": { - "@eslint/js": "^9.36.0", - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.3", - "eslint": "^9.36.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", + "@eslint/js": "^9.37.0", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.4", + "eslint": "^9.37.0", + "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-refresh": "^0.4.23", "globals": "^16.4.0", "prettier": "^3.6.2", - "typescript": "~5.8.3", - "typescript-eslint": "^8.44.0", - "vite": "^7.1.7" + "typescript": "~5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.9" } } diff --git a/src/Home.tsx b/src/Home.tsx index 1400cdf..5aa8d15 100644 --- a/src/Home.tsx +++ b/src/Home.tsx @@ -12,16 +12,6 @@ export function Home() { A collection of React performance examples and demos to help you understand various optimization techniques and patterns.

-

- The RenderToken shows render counts. When - that value increases, the component just re-rendered. It will be much - higher than a well implemented example. -

-

- Enable react-scan to see performance differences with DOM - render times. It will disable RenderToken. Refresh the page - to turn off react-scan -

{routes.map(({ id, path, title, description }) => ( @@ -34,7 +24,15 @@ export function Home() {

Demo App layout

Here is what the app looks like in each example. Each component is named - below + below. +

+

+ The RenderToken shows render counts. When + that value increases, the component just re-rendered. +

+

+ Enable react-scan to see performance differences with DOM + render times. It will disable RenderToken.

diff --git a/src/api/worlds.ts b/src/api/worlds.ts index 4f8b9eb..e2a7ed4 100644 --- a/src/api/worlds.ts +++ b/src/api/worlds.ts @@ -1,6 +1,14 @@ -import type { World } from "../types/World"; import { getNextWorld } from "../examples/zustand/data/solarSystemWorlds"; +export type World = { + id: string; + name: string; + distanceFromSun: string; + diameter: string; + orbitalPeriod: string; + type: string; +}; + export type WorldsResponse = { worlds: World[]; }; diff --git a/src/components/DebugInfo.tsx b/src/components/DebugInfo.tsx index 3fb1f7c..4774ea1 100644 --- a/src/components/DebugInfo.tsx +++ b/src/components/DebugInfo.tsx @@ -1,7 +1,7 @@ import { memo } from "react"; import css from "./css/DebugInfo.module.css"; import sharedStyles from "./css/shared.module.css"; -import type { World } from "../types/World.ts"; +import type { World } from "../api/worlds.ts"; type Snapshot = { selectedWorldId: string; diff --git a/src/examples/propDrilling/PropDrillingRenderDemo.tsx b/src/examples/propDrilling/PropDrillingRenderDemo.tsx index 794f9dd..285c0f0 100644 --- a/src/examples/propDrilling/PropDrillingRenderDemo.tsx +++ b/src/examples/propDrilling/PropDrillingRenderDemo.tsx @@ -1,11 +1,9 @@ import { useState } from "react"; import { useLoaderData, useRevalidator } from "react-router-dom"; import css from "../../components/css/DemoLayout.module.css"; -import { RenderToken } from "../../components/perf/RenderToken.tsx"; -import type { World } from "../../types/World.ts"; import { WorldApp } from "./exampleComponents/WorldApp.tsx"; import { DebugInfo } from "../../components/DebugInfo.tsx"; -import type { WorldsResponse } from "../../api/worlds.ts"; +import type { World, WorldsResponse } from "../../api/worlds.ts"; type Snapshot = { selectedWorldId: string; @@ -21,25 +19,12 @@ export function PropDrillingRenderDemo() {

Optimized Prop Drilling Demo

- This demo highlights an optimized prop drilling flow: state still lives + This demo highlights an optimized prop drilling flow. State still lives at the top, but children receive stable data and callbacks via - React.memo, useMemo, and{" "} + React.memo, useMemo, and useCallback. Keeping shapes consistent across layers prevents prop churn and keeps memoized children from re-rendering - unnecessarily—even if a dedicated state store would still be leaner. -

-

- The RenderToken shows render counts. When - that value increases, the component just re-rendered. This demo shows - noticeably lower counts across the tree than the naive version, making - it easier to see how the optimizations pay off in practice. -

- -

Instructions:

-

- Click Add world to simulate changing data. Select a - world from the World Selector to change the highlighted - item. + unnecessarily.

{ + onSnapshotChangeRef.current = onSnapshotChange; + }, [onSnapshotChange]); const stableOnSnapshotChange = useCallback( (snapshot: { selectedWorldId: string; hello: { worlds: World[] } }) => { diff --git a/src/examples/propDrilling/exampleComponents/worldSelector/WorldSelector.tsx b/src/examples/propDrilling/exampleComponents/worldSelector/WorldSelector.tsx index e3dfbbb..2187d34 100644 --- a/src/examples/propDrilling/exampleComponents/worldSelector/WorldSelector.tsx +++ b/src/examples/propDrilling/exampleComponents/worldSelector/WorldSelector.tsx @@ -4,7 +4,7 @@ import sharedStyles from "../../../../components/css/shared.module.css"; import css from "../../../../components/css/worldSelector/WorldSelector.module.css"; import { AddWorldButton } from "./AddWorld.tsx"; import { WorldIdButton } from "./WorldIdButton.tsx"; -import type { World } from "../../data/types.ts"; +import type { World } from "../../../../api/worlds"; type Props = { activeWorld: string; diff --git a/src/examples/propDrilling/exampleComponents/worldsViewer/World.tsx b/src/examples/propDrilling/exampleComponents/worldsViewer/World.tsx index a564b97..77cd23f 100644 --- a/src/examples/propDrilling/exampleComponents/worldsViewer/World.tsx +++ b/src/examples/propDrilling/exampleComponents/worldsViewer/World.tsx @@ -2,7 +2,7 @@ import { memo, useEffect, useRef } from "react"; import { RenderToken } from "../../../../components/perf/RenderToken.tsx"; import css from "../../../../components/css/worldsViewer/World.module.css"; import { WorldInfo } from "./WorldInfo.tsx"; -import type { World as WorldType } from "../../data/types.ts"; +import type { World as WorldType } from "../../../../api/worlds"; type Props = { world: WorldType; diff --git a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldInfo.tsx b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldInfo.tsx index 8e8a292..4e44f0c 100644 --- a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldInfo.tsx +++ b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldInfo.tsx @@ -1,7 +1,7 @@ import { memo, useMemo } from "react"; import { RenderToken } from "../../../../components/perf/RenderToken.tsx"; import css from "../../../../components/css/worldsViewer/WorldInfo.module.css"; -import type { World } from "../../data/types.ts"; +import type { World } from "../../../../api/worlds"; type Props = { world: World; diff --git a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldList.tsx b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldList.tsx index ca0e961..0d87bba 100644 --- a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldList.tsx +++ b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldList.tsx @@ -2,7 +2,7 @@ import { memo } from "react"; import { RenderToken } from "../../../../components/perf/RenderToken.tsx"; import css from "../../../../components/css/worldsViewer/WorldList.module.css"; import { World } from "./World.tsx"; -import type { World as WorldType } from "../../data/types.ts"; +import type { World as WorldType } from "../../../../api/worlds"; type Props = { worlds: WorldType[]; diff --git a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldsViewer.tsx b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldsViewer.tsx index c7310d9..0def90a 100644 --- a/src/examples/propDrilling/exampleComponents/worldsViewer/WorldsViewer.tsx +++ b/src/examples/propDrilling/exampleComponents/worldsViewer/WorldsViewer.tsx @@ -2,7 +2,7 @@ import { memo } from "react"; import { RenderToken } from "../../../../components/perf/RenderToken.tsx"; import sharedStyles from "../../../../components/css/shared.module.css"; import { WorldList } from "./WorldList.tsx"; -import type { World } from "../../data/types.ts"; +import type { World } from "../../../../api/worlds"; type Props = { worlds: World[]; diff --git a/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx b/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx index 7430b6a..1e7ca7e 100644 --- a/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx +++ b/src/examples/propDrillingNaive/PropDrillingNaiveRenderDemo.tsx @@ -1,10 +1,8 @@ import { useState, useEffect } from "react"; import css from "../../components/css/DemoLayout.module.css"; -import { RenderToken } from "../../components/perf/RenderToken.tsx"; -import type { World } from "../../types/World.ts"; import { PropDrillingWorldApp } from "./exampleComponents/WorldApp.tsx"; import { DebugInfo } from "../../components/DebugInfo.tsx"; -import { fetchWorlds } from "../../api/worlds.ts"; +import { fetchWorlds, type World } from "../../api/worlds.ts"; type Snapshot = { selectedWorldId: string; @@ -27,22 +25,8 @@ export function PropDrillingNaiveRenderDemo() {

Prop Drilling Render Demo

This intentionally clumsy demo uses anti-patterns commonly seen with - prop drilling. All state sits at the top of the tree, forcing every - child to receive new prop objects each render—causing relentless prop - churn, broken memoization, and a cascade of unnecessary updates. -

-

- The RenderToken shows render counts. When - that value increases, the component just re-rendered. Expect the counter - to spike across multiple components compared to healthier patterns, so - use it to spot how far the churn propagates. -

- -

Instructions:

-

- Click Add world to simulate changing data. Select a - world from the World Selector to change the highlighted - item. + prop drilling. Prop churn, broken memoization, and a cascade of + unnecessary updates.

(

🌤 Zustand + tanstack-query/react

-

Puts API state in tanstack/react-query and client state in Zustand.

- The WorldsSelector and WorldsViewer components - are siblings. The WorldsViewer component has a child - component WorldList. + Puts API state in tanstack-query/react and client state in Zustand. + Since tanstack-query is a specialized tool for managing api data, its + more fully featured for handling data specific needs.

- -

Instructions:

-

- Simulate API data changing by clicking Add world{" "} - (notice the loading state and error handling). Simulate client state - changing by clicking a different world in the{" "} - World Selector. -

-
diff --git a/src/examples/zustand-query/data/solarSystemWorlds.ts b/src/examples/zustand-query/data/solarSystemWorlds.ts index 4eed5c5..76a86c5 100644 --- a/src/examples/zustand-query/data/solarSystemWorlds.ts +++ b/src/examples/zustand-query/data/solarSystemWorlds.ts @@ -1,4 +1,4 @@ -import type { World } from "./types"; +import type { World } from "../../../api/worlds"; export const SOLAR_SYSTEM_WORLDS: World[] = [ { diff --git a/src/examples/zustand-query/data/types.ts b/src/examples/zustand-query/data/types.ts deleted file mode 100644 index bcfa475..0000000 --- a/src/examples/zustand-query/data/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type World = { - id: string; - name: string; - distanceFromSun: string; - diameter: string; - orbitalPeriod: string; - type: string; -}; diff --git a/src/examples/zustand/ZustandRenderDemo.tsx b/src/examples/zustand/ZustandRenderDemo.tsx index 644a86c..cf7ac7a 100644 --- a/src/examples/zustand/ZustandRenderDemo.tsx +++ b/src/examples/zustand/ZustandRenderDemo.tsx @@ -2,7 +2,6 @@ import { useEffect } from "react"; import { useLoaderData, useRevalidator } from "react-router-dom"; import css from "../../components/css/DemoLayout.module.css"; import { DebugInfo } from "../../components/DebugInfo.tsx"; -import { RenderToken } from "../../components/perf/RenderToken.tsx"; import { WorldApp } from "./exampleComponents/WorldApp.tsx"; import { useWorldStore } from "./data/WorldStore.tsx"; import type { WorldsResponse } from "../../api/worlds.ts"; @@ -31,16 +30,8 @@ export function ZustandRenderDemo() {

Zustand Render Demo

This demo shows you can prevent re-rendering without using prop - drilling. The RenderToken is a counter - which counts each render. When that value increases, the component just - re-rendered. -

- -

Instructions:

-

- Simulate API data changing by clicking Add world. - Simulate client state changing by clicking a different world in the{" "} - World Selector. + drilling. Because a zustand is used, its internal data pub/sub is more + efficient than React memoization.

diff --git a/src/examples/zustand/data/WorldStore.tsx b/src/examples/zustand/data/WorldStore.tsx index 0e71735..70ffb9d 100644 --- a/src/examples/zustand/data/WorldStore.tsx +++ b/src/examples/zustand/data/WorldStore.tsx @@ -1,5 +1,6 @@ import { create } from "zustand"; -import type { World } from "../../../types/World.ts"; + +import type { World } from "../../../api/worlds.ts"; interface Store { selectedWorldId: string; diff --git a/src/examples/zustand/data/solarSystemWorlds.ts b/src/examples/zustand/data/solarSystemWorlds.ts index b9a3d80..76a86c5 100644 --- a/src/examples/zustand/data/solarSystemWorlds.ts +++ b/src/examples/zustand/data/solarSystemWorlds.ts @@ -1,4 +1,4 @@ -import type { World } from "./types.ts"; +import type { World } from "../../../api/worlds"; export const SOLAR_SYSTEM_WORLDS: World[] = [ { diff --git a/src/examples/zustand/data/types.ts b/src/examples/zustand/data/types.ts deleted file mode 100644 index bcfa475..0000000 --- a/src/examples/zustand/data/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type World = { - id: string; - name: string; - distanceFromSun: string; - diameter: string; - orbitalPeriod: string; - type: string; -}; diff --git a/src/routes.ts b/src/routes.ts index c39e146..463f65e 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -36,14 +36,14 @@ export const routes: Route[] = [ navLabel: "🌤️ Zustand", title: "🌤️ Zustand", description: - "Both API and client state are in zustand. Optimized to prevent re-rendering. Props are passed only as lookup keys", + "Both API and client state are in zustand. Optimized to prevent re-rendering. Props are passed only as lookup keys.", }, { id: "zustand-query", path: "/zustand-query", - navLabel: "🌤 RQ+Zustand", - title: "🌤 tanstack-query/react + Zustand", + navLabel: "🌤 Zustand+tanstack", + title: "🌤 Zustand + tanstack-query/react", description: - "API state in tanstack-query/react, client state in zustand. Shows how you can combine state management", + "API state in tanstack-query/react, client state in zustand. Shows how you can combine state from multiple libraries.", }, ]; diff --git a/src/types/World.ts b/src/types/World.ts deleted file mode 100644 index fb48961..0000000 --- a/src/types/World.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Shared type definitions used across multiple examples. - * This ensures consistency and reduces coupling between examples. - */ - -export type World = { - id: string; - name: string; - distanceFromSun: string; - diameter: string; - orbitalPeriod: string; - type: string; -};