From f7248380c06dec0cc0625517b0e47c4958e549c7 Mon Sep 17 00:00:00 2001 From: Raz Date: Wed, 12 Aug 2026 11:06:16 +0300 Subject: [PATCH 1/7] Swap react-router-dom for TanStack Router (file-based) Behavior-identical router swap per the approved plan: - File routes under src/routes/ (pathless _shell layout keeps 404s blank; /table parent preserves the no-remount tab-switch guarantee) - Custom string-only parseSearch/stringifySearch: snapshot IDs exceed Number.MAX_SAFE_INTEGER and must never be JSON-parsed; URLSearchParams serialization keeps dup/cache_id URLs byte-identical - z.looseObject search schemas so unknown params survive carry-forward - Outlet context replaced by features/table/tableGraphData.ts - Mock-mode redirects moved to beforeLoad; QueryClientProvider added (unused until page migrations) - main.jsx/App.jsx replaced by main.tsx + routes; react-router-dom still installed until final removal commit Co-Authored-By: Claude Fable 5 --- frontend/.prettierignore | 1 + frontend/eslint.config.js | 16 +- frontend/index.html | 2 +- frontend/package-lock.json | 379 +++++++++++++++++- frontend/package.json | 3 + frontend/src/App.jsx | 96 ----- frontend/src/components/NavBar.jsx | 166 +++++--- frontend/src/components/PageLoader.tsx | 15 + frontend/src/features/table/tableGraphData.ts | 21 + frontend/src/hooks/useViewInGraph.js | 4 +- frontend/src/main.jsx | 25 -- frontend/src/main.tsx | 47 +++ frontend/src/pages/FileTreePage.jsx | 4 +- frontend/src/pages/GraphPage.jsx | 22 +- frontend/src/pages/HomePage.jsx | 5 +- frontend/src/pages/MetadataPage.jsx | 4 +- frontend/src/pages/SnapshotSelectionPage.jsx | 27 +- frontend/src/pages/TableLayout.jsx | 41 +- frontend/src/pages/TimelinePage.jsx | 4 +- frontend/src/routeTree.gen.ts | 263 ++++++++++++ frontend/src/routes/__root.tsx | 14 + frontend/src/routes/_shell.tsx | 11 + frontend/src/routes/_shell/docs.tsx | 6 + frontend/src/routes/_shell/index.tsx | 16 + .../src/routes/_shell/snapshots-selection.tsx | 18 + frontend/src/routes/_shell/table/filetree.tsx | 6 + frontend/src/routes/_shell/table/graph.tsx | 8 + frontend/src/routes/_shell/table/index.tsx | 5 + frontend/src/routes/_shell/table/metadata.tsx | 6 + frontend/src/routes/_shell/table/route.tsx | 8 + frontend/src/routes/_shell/table/timeline.tsx | 6 + frontend/src/shared/lib/searchParams.ts | 45 +++ frontend/vite.config.ts | 4 + 33 files changed, 1062 insertions(+), 236 deletions(-) delete mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/components/PageLoader.tsx create mode 100644 frontend/src/features/table/tableGraphData.ts delete mode 100644 frontend/src/main.jsx create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/routeTree.gen.ts create mode 100644 frontend/src/routes/__root.tsx create mode 100644 frontend/src/routes/_shell.tsx create mode 100644 frontend/src/routes/_shell/docs.tsx create mode 100644 frontend/src/routes/_shell/index.tsx create mode 100644 frontend/src/routes/_shell/snapshots-selection.tsx create mode 100644 frontend/src/routes/_shell/table/filetree.tsx create mode 100644 frontend/src/routes/_shell/table/graph.tsx create mode 100644 frontend/src/routes/_shell/table/index.tsx create mode 100644 frontend/src/routes/_shell/table/metadata.tsx create mode 100644 frontend/src/routes/_shell/table/route.tsx create mode 100644 frontend/src/routes/_shell/table/timeline.tsx create mode 100644 frontend/src/shared/lib/searchParams.ts diff --git a/frontend/.prettierignore b/frontend/.prettierignore index 962215c..111deed 100644 --- a/frontend/.prettierignore +++ b/frontend/.prettierignore @@ -1,3 +1,4 @@ dist public package-lock.json +src/routeTree.gen.ts diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index d93e1b3..495f146 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -43,7 +43,7 @@ const philosophyRules = { }; export default tseslint.config( - { ignores: ["dist", "public", "node_modules"] }, + { ignores: ["dist", "public", "node_modules", "src/routeTree.gen.ts"] }, // eslint-disable comments must carry a justification (PHILOSOPHY.md 'Tooling & enforcement') { @@ -85,6 +85,20 @@ export default tseslint.config( }, }, + // TanStack Router file routes: exporting `Route` and throwing redirect() + // are the router's own documented pattern — + // https://tanstack.com/router/latest/docs/framework/react/routing/file-based-routing + { + files: ["src/routes/**/*.tsx"], + rules: { + "react-refresh/only-export-components": [ + "error", + { allowExportNames: ["Route"] }, + ], + "@typescript-eslint/only-throw-error": "off", + }, + }, + // Component files: 200-line cap (lint-enforced per PHILOSOPHY.md) { files: ["**/*.tsx"], diff --git a/frontend/index.html b/frontend/index.html index be359aa..5d6075c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -9,6 +9,6 @@
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e172c1f..49547ef 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "icegraph-frontend", "dependencies": { "@tanstack/react-query": "^5.101.4", + "@tanstack/react-router": "^1.170.25", "clsx": "^2.1.1", "json-bigint": "^1.0.0", "react": "^19.2.8", @@ -21,6 +22,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@tailwindcss/vite": "^4.0.0", + "@tanstack/router-plugin": "^1.168.29", "@types/json-bigint": "^1.0.4", "@types/node": "^26.2.0", "@types/react": "^19.2.18", @@ -1807,6 +1809,19 @@ "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, + "node_modules/@tanstack/history": { + "version": "1.162.1", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.1.tgz", + "integrity": "sha512-DR9t6lfLVdrjgCwpglrR9DR7Ok8/HlXjcOE+goWXF3zyuLUO/ug7vMbSFxTqrQTtbRghJfyhmIZ0S6LhPIy44w==", + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { "version": "5.101.4", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz", @@ -1833,6 +1848,187 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-router": { + "version": "1.170.25", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.25.tgz", + "integrity": "sha512-XiWYvkLAGhcZHhV2xUvicpF/VfTVSnewP6CqviDONAjmD50tBob5x/93u2W8QZAc/JgkPd+jijCmu6t4NCzmew==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.1", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "1.171.21", + "isbot": "^5.1.22" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.9.3", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/router-core": { + "version": "1.171.21", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.21.tgz", + "integrity": "sha512-t6xUHBO94nQDrPHPh6ag5UuKHWIkVjq9s63q2ctbxgzq3vtSoGKmAIdLD5o+NU6JUS1ppXRHgL0YGzEHvH32Ng==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.1", + "cookie-es": "^3.0.0", + "seroval": "^1.6.2", + "seroval-plugins": "^1.6.2" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.167.27", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.167.27.tgz", + "integrity": "sha512-bfV44Nyy7XeSCCuvQWGc3qudqXfOqAwKiWXNymAuK+hy24vs3E75Aw1zMz97ajHXMjxm4nFVVjEnwZ/cDR74ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.21", + "@tanstack/router-utils": "1.162.2", + "@tanstack/virtual-file-routes": "1.162.0", + "jiti": "^2.7.0", + "magic-string": "^0.30.21", + "prettier": "^3.5.0", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-plugin": { + "version": "1.168.29", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.168.29.tgz", + "integrity": "sha512-h1ZbnIvbAKIFeQ6JBRJ/xBP17tBdwjwzPBKVKUdVNYiob07SkrQLhqz+s41WtbNmHey5kQWN3HqP16Vl062bqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.21", + "@tanstack/router-generator": "1.167.27", + "@tanstack/router-utils": "1.162.2", + "chokidar": "^5.0.0", + "unplugin": "^3.0.0", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": ">=1.0.2 || ^2.0.0", + "@tanstack/react-router": "^1.170.25", + "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", + "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", + "webpack": ">=5.92.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "@tanstack/react-router": { + "optional": true + }, + "vite": { + "optional": true + }, + "vite-plugin-solid": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@tanstack/router-utils": { + "version": "1.162.2", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.162.2.tgz", + "integrity": "sha512-hTWqJtqIFFdvuCl8WXNyrodp2L9zo2G37xKRrcVmVRWpAB2h+U1LuRAfS4tsFTiWOIoE/B+WDVFB8JpoEdw6jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.28.5", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "ansis": "^4.1.0", + "babel-dead-code-elimination": "^1.0.12", + "diff": "^8.0.2", + "pathe": "^2.0.3", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-file-routes": { + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.162.0.tgz", + "integrity": "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tweenjs/tween.js": { "version": "25.0.0", "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz", @@ -2288,6 +2484,29 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", + "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, "node_modules/babel-plugin-react-compiler": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", @@ -2420,6 +2639,22 @@ "node": ">=12" } }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -2513,6 +2748,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2786,6 +3027,16 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.328", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.328.tgz", @@ -3390,6 +3641,15 @@ "dev": true, "license": "MIT" }, + "node_modules/isbot": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.2.1.tgz", + "integrity": "sha512-dJ+LpKyClQZ7NG+j3OensC/mAZkGpukE9YUrgPYvAZj2doVL0edfDgywTUh5CXa0o+nW9a1V9e5+CJTX8+SxRw==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3407,9 +3667,9 @@ } }, "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "dev": true, "license": "MIT", "bin": { @@ -4033,6 +4293,13 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4240,6 +4507,20 @@ "react-dom": ">=16.8" } }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4318,6 +4599,27 @@ "semver": "bin/semver.js" } }, + "node_modules/seroval": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.6.2.tgz", + "integrity": "sha512-mPT+SD2TrlB6wvte1KkYOYUkubaTbd6pZ/6Kk3C9nxzrHmCZyhxOO7XGAeL7f+yLKZglzGtM9odUVvg/EhO+vQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.6.2.tgz", + "integrity": "sha512-TfxuUjlbBESzUOWdTkTKqvSmav0ABym+itetDXLK6mDz8SmrpdI30aF8RTXE8Bvq+tH/1yIDkvy3W0lfQb1ipQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4596,6 +4898,61 @@ "dev": true, "license": "MIT" }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/until-async": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", @@ -4647,6 +5004,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "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", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", @@ -4722,6 +5088,13 @@ } } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index e8d865a..cc6b82a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,8 @@ "msw:init": "msw init public/" }, "dependencies": { + "@tanstack/react-query": "^5.101.4", + "@tanstack/react-router": "^1.170.25", "clsx": "^2.1.1", "json-bigint": "^1.0.0", "react": "^19.2.8", @@ -29,6 +31,7 @@ "@eslint-community/eslint-plugin-eslint-comments": "^4.7.2", "@eslint/js": "^10.0.1", "@tailwindcss/vite": "^4.0.0", + "@tanstack/router-plugin": "^1.168.29", "@types/json-bigint": "^1.0.4", "@types/node": "^26.2.0", "@types/react": "^19.2.18", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx deleted file mode 100644 index f75c4a5..0000000 --- a/frontend/src/App.jsx +++ /dev/null @@ -1,96 +0,0 @@ -import { lazy, Suspense } from "react"; -import { Navigate, Route, Routes } from "react-router-dom"; -import { MOCK_HOME_ROUTE, IS_MOCK } from "./appConstants"; -import NavBar from "./components/NavBar"; -import { TableSpecsProvider } from "./context/TableSpecsContext"; -import logo from "./assets/icegraph.png"; - -const HomePage = lazy(() => import("./pages/HomePage")); -const SnapshotSelectionPage = lazy( - () => import("./pages/SnapshotSelectionPage"), -); -const DocsPage = lazy(() => import("./pages/DocsPage")); -const TableLayout = lazy(() => import("./pages/TableLayout")); -const GraphPage = lazy(() => import("./pages/GraphPage")); -const MetadataPage = lazy(() => import("./pages/MetadataPage")); -const TimelinePage = lazy(() => import("./pages/TimelinePage")); -const FileTreePage = lazy(() => import("./pages/FileTreePage")); - -function PageLoader() { - return ( -
- IceGraph -
- Loading - . - . - . -
-
- ); -} - -function Layout({ children }) { - return ( -
- - {children} -
- ); -} - -export default function App() { - return ( - - }> - - - ) : ( - - - - ) - } - /> - - ) : ( - - - - ) - } - /> - - - - } - /> - - - - } - > - } /> - } /> - } /> - } /> - - - - - ); -} diff --git a/frontend/src/components/NavBar.jsx b/frontend/src/components/NavBar.jsx index 7c8230b..1bc3cef 100644 --- a/frontend/src/components/NavBar.jsx +++ b/frontend/src/components/NavBar.jsx @@ -1,22 +1,16 @@ import { useEffect, useRef, useState } from "react"; import { - NavLink, + Link, useLocation, - useMatch, + useMatchRoute, useNavigate, - useSearchParams, -} from "react-router-dom"; + useSearch, +} from "@tanstack/react-router"; import logo from "../assets/icegraph.png"; import CatalogTableList from "./CatalogTableList"; import { useTableSpecs } from "../context/TableSpecsContext"; import { cacheData, clearCachedData } from "../utils/cacheUtils"; -import { - BASE_PATH, - IS_MOCK, - MOCK_HOME, - MOCK_HOME_ROUTE, - MOCK_TABLE, -} from "../appConstants"; +import { BASE_PATH, IS_MOCK, MOCK_HOME, MOCK_TABLE } from "../appConstants"; import { UI_ERROR_TEXT_SPACED_CLASS, UI_FORM_LABEL_CLASS, @@ -28,10 +22,11 @@ import { export default function NavBar() { const location = useLocation(); - const [searchParams] = useSearchParams(); + const search = useSearch({ strict: false }); const navigate = useNavigate(); - const isTablePage = useMatch("/table/*"); - const tableName = searchParams.get("table"); + const matchRoute = useMatchRoute(); + const isTablePage = Boolean(matchRoute({ to: "/table", fuzzy: true })); + const tableName = search.table ?? null; const { detailsOpen, setDetailsOpen, @@ -73,7 +68,7 @@ export default function NavBar() { useEffect(() => { setMenuOpen(false); setTablePickerOpen(false); - }, [location.pathname, location.search]); + }, [location.pathname, location.searchStr]); useEffect(() => { if (!tablePickerOpen) return; @@ -97,14 +92,12 @@ export default function NavBar() { const idx = parseInt(e.key) - 1; if (idx >= 0 && idx < tabs.length) { e.preventDefault(); - navigate(`/table/${tabs[idx]}${location.search}`); + navigate({ to: `/table/${tabs[idx]}`, search: (prev) => prev }); } }; window.addEventListener("keydown", handleKey); return () => window.removeEventListener("keydown", handleKey); - }, [isTablePage, navigate, location.search]); - - const tabSearch = location.search; + }, [isTablePage, navigate]); function openTablePicker() { setPickerTableName(tableName || ""); @@ -241,19 +234,23 @@ export default function NavBar() { }, 2000); }; - const tabClass = ({ isActive }) => - `text-sm font-medium px-1 py-0.5 border-b-2 transition ${ - isActive - ? "border-accent text-white" - : "border-transparent text-slate-400 hover:text-white hover:border-slate-500" - }`; + const tabActiveProps = { + className: + "text-sm font-medium px-1 py-0.5 border-b-2 transition border-accent text-white", + }; + const tabInactiveProps = { + className: + "text-sm font-medium px-1 py-0.5 border-b-2 transition border-transparent text-slate-400 hover:text-white hover:border-slate-500", + }; - const mobileTabClass = ({ isActive }) => - `text-sm font-medium px-3 py-2 rounded-md transition text-left ${ - isActive - ? "bg-accent-muted text-white" - : "text-slate-400 hover:text-white hover:bg-surface-hover" - }`; + const mobileTabActiveProps = { + className: + "text-sm font-medium px-3 py-2 rounded-md transition text-left bg-accent-muted text-white", + }; + const mobileTabInactiveProps = { + className: + "text-sm font-medium px-3 py-2 rounded-md transition text-left text-slate-400 hover:text-white hover:bg-surface-hover", + }; return (