diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..3d517e63 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.2 + hooks: + - id: ruff-check + args: [--fix] + - id: ruff-format + + - repo: https://github.com/hoxbro/ty-pre-commit + rev: v0.0.17 + hooks: + - id: ty-check + args: [--ignore, unresolved-import] diff --git a/viewer_new/.gitignore b/viewer_new/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/viewer_new/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/viewer_new/README.md b/viewer_new/README.md new file mode 100644 index 00000000..975cbbb6 --- /dev/null +++ b/viewer_new/README.md @@ -0,0 +1,19 @@ +# `viewer_new` + +Standalone Bun + Vite + React viewer for DeepRetro. + +## Commands + +```bash +bun install +bun run dev +bun run build +bun run typecheck +bun run test +``` + +## Notes + +- The legacy `viewer/` app is intentionally untouched. +- `bun run sync:config` copies the root `config/advanced_settings.json` into `public/advanced-settings.json`. +- Runtime backend configuration lives in `public/runtime-config.json`. diff --git a/viewer_new/bun.lock b/viewer_new/bun.lock new file mode 100644 index 00000000..64f2d510 --- /dev/null +++ b/viewer_new/bun.lock @@ -0,0 +1,766 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "viewer_new", + "dependencies": { + "@codemirror/lang-json": "^6.0.2", + "@rdkit/rdkit": "^2025.3.4-1.0.0", + "@tanstack/react-query": "^5.90.21", + "@uiw/react-codemirror": "^4.25.7", + "@xyflow/react": "^12.10.1", + "elkjs": "^0.11.1", + "lucide-react": "^0.577.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6", + "zustand": "^5.0.11", + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^25.3.5", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "jsdom": "^28.1.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1", + "vitest": "^4.0.18", + }, + }, + }, + "packages": { + "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="], + + "@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="], + + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.0.1", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-color-parser": "^4.0.2", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.2.6" } }, "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw=="], + + "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.8.1", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.6" } }, "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ=="], + + "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], + + "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], + + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.20.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A=="], + + "@codemirror/commands": ["@codemirror/commands@6.10.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-vvX1fsih9HledO1c9zdotZYUZnE4xV0m6i3m25s5DIfXofuprk6cRcLUZvSk3CASUbwjQX21tOGbkY2BH8TpnQ=="], + + "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], + + "@codemirror/language": ["@codemirror/language@6.12.2", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-jEPmz2nGGDxhRTg3lTpzmIyGKxz3Gp3SJES4b0nAuE5SWQoKdT5GoQ69cwMmFd+wvFUhYirtDTr0/DRHpQAyWg=="], + + "@codemirror/lint": ["@codemirror/lint@6.9.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA=="], + + "@codemirror/search": ["@codemirror/search@6.6.0", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.37.0", "crelt": "^1.0.5" } }, "sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw=="], + + "@codemirror/state": ["@codemirror/state@6.5.4", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw=="], + + "@codemirror/theme-one-dark": ["@codemirror/theme-one-dark@6.1.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/highlight": "^1.0.0" } }, "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA=="], + + "@codemirror/view": ["@codemirror/view@6.39.16", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-m6S22fFpKtOWhq8HuhzsI1WzUP/hB9THbDj0Tl5KX4gbO6Y91hwBl7Yky33NdvB6IffuRFiBxf1R8kJMyXmA4Q=="], + + "@csstools/color-helpers": ["@csstools/color-helpers@6.0.2", "", {}, "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q=="], + + "@csstools/css-calc": ["@csstools/css-calc@3.1.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ=="], + + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.2", "", { "dependencies": { "@csstools/color-helpers": "^6.0.2", "@csstools/css-calc": "^3.1.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.0", "", {}, "sha512-H4tuz2nhWgNKLt1inYpoVCfbJbMwX/lQKp3g69rrrIMIYlFD9+zTykOKhNR8uGrAmbS/kT9n6hTFkmDkxLgeTA=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="], + + "@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + + "@exodus/bytes": ["@exodus/bytes@1.15.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@lezer/common": ["@lezer/common@1.5.1", "", {}, "sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw=="], + + "@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="], + + "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], + + "@lezer/lr": ["@lezer/lr@1.4.8", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], + + "@rdkit/rdkit": ["@rdkit/rdkit@2025.3.4-1.0.0", "", {}, "sha512-MJzNoeW2SWt2KCdIibfY213uLWyHZjFfS2ntJ/HuYNHdu6dEiim7jb6ZMU9wnt9Oovkc85BHMuupxkufIMvftQ=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.90.21", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="], + + "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], + + "@testing-library/user-event": ["@testing-library/user-event@14.6.1", "", { "peerDependencies": { "@testing-library/dom": ">=7.21.4" } }, "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.56.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/type-utils": "8.56.1", "@typescript-eslint/utils": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.56.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.56.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.56.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.56.1", "@typescript-eslint/types": "^8.56.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1" } }, "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.56.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.56.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.56.1", "", { "dependencies": { "@typescript-eslint/types": "8.56.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw=="], + + "@uiw/codemirror-extensions-basic-setup": ["@uiw/codemirror-extensions-basic-setup@4.25.7", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-tPV/AGjF4yM22D5mnyH7EuYBkWO05wF5Y4x3lmQJo6LuHmhjh0RQsVDjqeIgNOkXT3UO9OdkL4dzxw465/JZVg=="], + + "@uiw/react-codemirror": ["@uiw/react-codemirror@4.25.7", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", "@uiw/codemirror-extensions-basic-setup": "4.25.7", "codemirror": "^6.0.0" }, "peerDependencies": { "@codemirror/view": ">=6.0.0", "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-s/EbEe0dFANWEgfLbfdIrrOGv0R7M1XhkKG3ShroBeH6uP9pVNQy81YHOLRCSVcytTp9zAWRNfXR/+XxZTvV7w=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.4", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA=="], + + "@vitest/expect": ["@vitest/expect@4.0.18", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ=="], + + "@vitest/mocker": ["@vitest/mocker@4.0.18", "", { "dependencies": { "@vitest/spy": "4.0.18", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.18", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw=="], + + "@vitest/runner": ["@vitest/runner@4.0.18", "", { "dependencies": { "@vitest/utils": "4.0.18", "pathe": "^2.0.3" } }, "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA=="], + + "@vitest/spy": ["@vitest/spy@4.0.18", "", {}, "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw=="], + + "@vitest/utils": ["@vitest/utils@4.0.18", "", { "dependencies": { "@vitest/pretty-format": "4.0.18", "tinyrainbow": "^3.0.3" } }, "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA=="], + + "@xyflow/react": ["@xyflow/react@12.10.1", "", { "dependencies": { "@xyflow/system": "0.0.75", "classcat": "^5.0.3", "zustand": "^4.4.0" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-5eSWtIK/+rkldOuFbOOz44CRgQRjtS9v5nufk77DV+XBnfCGL9HAQ8PG00o2ZYKqkEU/Ak6wrKC95Tu+2zuK3Q=="], + + "@xyflow/system": ["@xyflow/system@0.0.75", "", { "dependencies": { "@types/d3-drag": "^3.0.7", "@types/d3-interpolate": "^3.0.4", "@types/d3-selection": "^3.0.10", "@types/d3-transition": "^3.0.8", "@types/d3-zoom": "^3.0.8", "d3-drag": "^3.0.0", "d3-interpolate": "^3.0.1", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0" } }, "sha512-iXs+AGFLi8w/VlAoc/iSxk+CxfT6o64Uw/k0CKASOPqjqz6E0rb5jFZgJtXGZCpfQI6OQpu5EnumP5fGxQheaQ=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="], + + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001777", "", {}, "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="], + + "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="], + + "cssstyle": ["cssstyle@6.2.0", "", { "dependencies": { "@asamuzakjp/css-color": "^5.0.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.28", "css-tree": "^3.1.0", "lru-cache": "^11.2.6" } }, "sha512-Fm5NvhYathRnXNVndkUsCCuR63DCLVVwGOOwQw782coXFi5HhkXdu289l59HlXZBawsyNccXfWRYvLzcDCdDig=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.307", "", {}, "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg=="], + + "elkjs": ["elkjs@0.11.1", "", {}, "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg=="], + + "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], + + "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.26", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.4", "", {}, "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsdom": ["jsdom@28.1.0", "", { "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.8.1", "@bramus/specificity": "^2.4.2", "@exodus/bytes": "^1.11.0", "cssstyle": "^6.0.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "undici": "^7.21.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], + + "lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], + + "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], + + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + + "strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + + "tldts": ["tldts@7.0.25", "", { "dependencies": { "tldts-core": "^7.0.25" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w=="], + + "tldts-core": ["tldts-core@7.0.25", "", {}, "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw=="], + + "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], + + "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], + + "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.56.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.56.1", "@typescript-eslint/parser": "8.56.1", "@typescript-eslint/typescript-estree": "8.56.1", "@typescript-eslint/utils": "8.56.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ=="], + + "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], + + "vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + + "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + + "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], + + "whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + + "zustand": ["zustand@5.0.11", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="], + + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + + "@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], + + "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + } +} diff --git a/viewer_new/eslint.config.js b/viewer_new/eslint.config.js new file mode 100644 index 00000000..5e6b472f --- /dev/null +++ b/viewer_new/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/viewer_new/index.html b/viewer_new/index.html new file mode 100644 index 00000000..f86e1550 --- /dev/null +++ b/viewer_new/index.html @@ -0,0 +1,12 @@ + + + + + + DeepRetro Viewer + + +
+ + + diff --git a/viewer_new/package.json b/viewer_new/package.json new file mode 100644 index 00000000..7076c88c --- /dev/null +++ b/viewer_new/package.json @@ -0,0 +1,51 @@ +{ + "name": "viewer_new", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "sync:config": "node ./scripts/sync-public-config.ts", + "predev": "node ./scripts/sync-public-config.ts", + "prebuild": "node ./scripts/sync-public-config.ts", + "pretest": "node ./scripts/sync-public-config.ts", + "pretypecheck": "node ./scripts/sync-public-config.ts", + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc -b --pretty false", + "test": "vitest run", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@codemirror/lang-json": "^6.0.2", + "@rdkit/rdkit": "^2025.3.4-1.0.0", + "@tanstack/react-query": "^5.90.21", + "@uiw/react-codemirror": "^4.25.7", + "@xyflow/react": "^12.10.1", + "elkjs": "^0.11.1", + "lucide-react": "^0.577.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "zod": "^4.3.6", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^25.3.5", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "jsdom": "^28.1.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.48.0", + "vite": "^7.3.1", + "vitest": "^4.0.18" + } +} diff --git a/viewer_new/public/advanced-settings.json b/viewer_new/public/advanced-settings.json new file mode 100644 index 00000000..9bb51651 --- /dev/null +++ b/viewer_new/public/advanced-settings.json @@ -0,0 +1,59 @@ +{ + "llm_models": { + "claude3": { + "internal_name": "claude-3-opus-20240229", + "display_name": "Claude 3 Opus", + "supports_advanced_prompt": true, + "supports_stability_check": true, + "supports_hallucination_check": true, + "supports_protecting_group_feature": false + }, + "claude37": { + "internal_name": "anthropic/claude-3-7-sonnet-20250219", + "display_name": "Claude 3.7 Sonnet", + "supports_advanced_prompt": true, + "supports_stability_check": true, + "supports_hallucination_check": true, + "supports_protecting_group_feature": false + }, + "deepseek": { + "internal_name": "fireworks_ai/accounts/fireworks/models/deepseek-r1", + "display_name": "DeepSeek", + "supports_advanced_prompt": true, + "supports_stability_check": true, + "supports_hallucination_check": true, + "supports_protecting_group_feature": false + }, + "claude4opus": { + "internal_name": "anthropic/claude-opus-4-20250514", + "display_name": "Claude 4 Opus", + "supports_advanced_prompt": true, + "supports_stability_check": true, + "supports_hallucination_check": true, + "supports_protecting_group_feature": false + }, + "claude4sonnet": { + "internal_name": "anthropic/claude-sonnet-4-20250514", + "display_name": "Claude 4 Sonnet", + "supports_advanced_prompt": true, + "supports_stability_check": true, + "supports_hallucination_check": true, + "supports_protecting_group_feature": false + } + }, + "az_models": { + "USPTO": { "display_name": "USPTO" }, + "Pistachio_25": { "display_name": "Pistachio (25)" }, + "Pistachio_50": { "display_name": "Pistachio (50)" }, + "Pistachio_100": { "display_name": "Pistachio (100)" }, + "Pistachio_100+": { "display_name": "Pistachio (100+)" } + }, + "defaults": { + "model_type": "claude4sonnet", + "advanced_prompt": false, + "model_version": "USPTO", + "stability_flag": true, + "hallucination_check": true, + "use_protecting_group_feature": false + } +} \ No newline at end of file diff --git a/viewer_new/public/runtime-config.json b/viewer_new/public/runtime-config.json new file mode 100644 index 00000000..9acef2bb --- /dev/null +++ b/viewer_new/public/runtime-config.json @@ -0,0 +1,24 @@ +{ + "instances": [ + { + "id": "instance1", + "label": "Pathway 1", + "baseUrl": "http://localhost:5000", + "defaults": { + "model_type": "claude4sonnet", + "advanced_prompt": true, + "model_version": "Pistachio_100+", + "stability_flag": true, + "hallucination_check": true, + "use_protecting_group_feature": false + } + } + ], + "endpoints": { + "retrosynthesis": "/api/retrosynthesis", + "rerun": "/api/rerun_retrosynthesis", + "partialRerun": "/api/partial_rerun", + "saveEdited": "/api/save_edited_result", + "health": "/api/health" + } +} diff --git a/viewer_new/scripts/sync-public-config.ts b/viewer_new/scripts/sync-public-config.ts new file mode 100644 index 00000000..c0b21457 --- /dev/null +++ b/viewer_new/scripts/sync-public-config.ts @@ -0,0 +1,12 @@ +import { copyFile, mkdir } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const source = resolve(scriptDir, "../../config/advanced_settings.json"); +const target = resolve(scriptDir, "../public/advanced-settings.json"); + +await mkdir(dirname(target), { recursive: true }); +await copyFile(source, target); + +console.log(`Synced advanced settings to ${target}`); diff --git a/viewer_new/src/api/flaskClient.ts b/viewer_new/src/api/flaskClient.ts new file mode 100644 index 00000000..b2b791f0 --- /dev/null +++ b/viewer_new/src/api/flaskClient.ts @@ -0,0 +1,77 @@ +import type { + AnalysisRequest, + PathwayResult, + ViewerRuntimeConfig, +} from "../types/viewer"; + +type InstanceEndpointConfig = { + baseUrl: string; + endpoints: ViewerRuntimeConfig["endpoints"]; + apiKey: string; +}; + +async function requestJson( + url: string, + init: RequestInit, +): Promise { + const response = await fetch(url, init); + const data = (await response.json().catch(() => ({}))) as T & { + error?: string; + }; + + if (!response.ok) { + throw new Error(data.error || `Request failed with ${response.status}`); + } + + return data; +} + +function withHeaders(apiKey: string) { + return { + "Content-Type": "application/json", + "X-API-KEY": apiKey, + }; +} + +export function createFlaskClient(config: InstanceEndpointConfig) { + const buildUrl = (path: string) => `${config.baseUrl}${path}`; + + return { + async retrosynthesis(body: AnalysisRequest) { + return requestJson(buildUrl(config.endpoints.retrosynthesis), { + method: "POST", + headers: withHeaders(config.apiKey), + body: JSON.stringify(body), + }); + }, + async rerun(body: AnalysisRequest) { + return requestJson(buildUrl(config.endpoints.rerun), { + method: "POST", + headers: withHeaders(config.apiKey), + body: JSON.stringify(body), + }); + }, + async partialRerun(body: AnalysisRequest & { steps: string }) { + return requestJson(buildUrl(config.endpoints.partialRerun), { + method: "POST", + headers: withHeaders(config.apiKey), + body: JSON.stringify(body), + }); + }, + async saveEditedResult(smiles: string, result: PathwayResult) { + return requestJson<{ message: string }>(buildUrl(config.endpoints.saveEdited), { + method: "POST", + headers: withHeaders(config.apiKey), + body: JSON.stringify({ smiles, result }), + }); + }, + async health() { + return requestJson<{ status: string }>(buildUrl(config.endpoints.health), { + method: "GET", + headers: { + "X-API-KEY": config.apiKey, + }, + }); + }, + }; +} diff --git a/viewer_new/src/app/App.tsx b/viewer_new/src/app/App.tsx new file mode 100644 index 00000000..13a7b550 --- /dev/null +++ b/viewer_new/src/app/App.tsx @@ -0,0 +1,392 @@ +import { useMemo, useState } from "react"; +import { Download, KeyRound, Play, RefreshCcw, Save, Sparkles, Upload } from "lucide-react"; + +import { GraphCanvas } from "../features/graph/GraphCanvas"; +import { InspectorPanel } from "../features/inspector/InspectorPanel"; +import { RunSidebar } from "../features/runs/RunSidebar"; +import { UploadPanel } from "../features/upload/UploadPanel"; +import { EditorDrawer } from "../features/editor/EditorDrawer"; +import { useBootstrapViewer, useViewerActions } from "../features/runs/useViewerActions"; +import { useViewerStore } from "../features/runs/store"; +import { canPartialRerunStep, getStep } from "../lib/pathway"; +import type { HealthStatusMap } from "../types/viewer"; + +function summarizeHealthStates(states: HealthStatusMap) { + const values = Object.values(states); + if (!values.length) { + return "Health unknown"; + } + if (values.every((value) => value.state === "healthy")) { + return "All backends healthy"; + } + if (values.some((value) => value.state === "healthy")) { + return "Backend health degraded"; + } + return "Backends unavailable"; +} + +export function App() { + useBootstrapViewer(); + + const runtimeConfig = useViewerStore((state) => state.runtimeConfig); + const advancedSettings = useViewerStore((state) => state.advancedSettings); + const health = useViewerStore((state) => state.health); + const runs = useViewerStore((state) => state.runs); + const instanceSettings = useViewerStore((state) => state.instanceSettings); + const mode = useViewerStore((state) => state.mode); + const apiKey = useViewerStore((state) => state.apiKey); + const currentSmiles = useViewerStore((state) => state.currentSmiles); + const activeRunKey = useViewerStore((state) => state.activeRunKey); + const selectedStepId = useViewerStore((state) => state.selectedStepId); + const editorOpen = useViewerStore((state) => state.editorOpen); + const setMode = useViewerStore((state) => state.setMode); + const setApiKey = useViewerStore((state) => state.setApiKey); + const setCurrentSmiles = useViewerStore((state) => state.setCurrentSmiles); + const setActiveRun = useViewerStore((state) => state.setActiveRun); + const setSelectedStep = useViewerStore((state) => state.setSelectedStep); + const updateInstanceSetting = useViewerStore((state) => state.updateInstanceSetting); + const setEditorOpen = useViewerStore((state) => state.setEditorOpen); + + const [feedback, setFeedback] = useState(""); + const [error, setError] = useState(""); + + const { + activeRun, + isAnalyzing, + isSaving, + isPartialRerunning, + analyzeAll, + loadUploadedResult, + applyStructuredStepEdit, + applyRawJsonEdit, + saveRunEdits, + partialRerun, + } = useViewerActions(); + + const handleUploadedFiles = (files: Array<{ fileName: string; input: unknown }>) => { + const loadedRunKeys = files.map(({ fileName, input }) => + loadUploadedResult(fileName, input), + ); + const latestRunKey = loadedRunKeys[loadedRunKeys.length - 1]; + if (latestRunKey) { + setActiveRun(latestRunKey); + } + setFeedback( + `Loaded ${files.length} file${files.length === 1 ? "" : "s"} into the new viewer.`, + ); + }; + + const selectedStep = useMemo( + () => + activeRun?.rawResult && selectedStepId + ? getStep(activeRun.rawResult, selectedStepId) + : undefined, + [activeRun?.rawResult, selectedStepId], + ); + + const partialRerunState = canPartialRerunStep(selectedStep); + const saveDisabled = !activeRun || activeRun.source !== "api" || !activeRun.dirty || isSaving; + const healthSummary = summarizeHealthStates(health); + + if (!runtimeConfig || !advancedSettings) { + return ( +
+
+

DeepRetro

+

Loading `viewer_new`

+

Fetching runtime config, advanced settings, and backend capabilities.

+
+
+ ); + } + + return ( +
+
+
+
+ +
+
+

DeepRetro Viewer

+

Retrosynthesis Workbench

+
+
+ +
+
+ + +
+ + + + + + + + + + + + +
+ +
+ {healthSummary} + {feedback ? {feedback} : null} + {error ? {error} : null} +
+
+ +
+ { + try { + setError(""); + setFeedback(""); + handleUploadedFiles(files); + } catch (nextError) { + setError(nextError instanceof Error ? nextError.message : "Upload failed."); + } + }} + onUpdateSetting={updateInstanceSetting} + /> + +
+ {mode === "upload" ? ( + { + try { + setError(""); + setFeedback(""); + handleUploadedFiles(files); + } catch (nextError) { + setError(nextError instanceof Error ? nextError.message : "Upload failed."); + } + }} + /> + ) : null} + +
+
+
+

Pathway graph

+

{activeRun?.label ?? "No run selected"}

+
+ {activeRun?.dirty ? local edits pending : null} +
+ + setSelectedStep(stepId)} + onEditStep={(stepId) => { + setSelectedStep(stepId); + setEditorOpen(true); + }} + onPartialRerunStep={async (stepId) => { + if (!activeRunKey) { + return; + } + + try { + setError(""); + setFeedback(""); + await partialRerun(activeRunKey, stepId); + setFeedback(`Partial rerun completed from step ${stepId}.`); + } catch (nextError) { + setError( + nextError instanceof Error ? nextError.message : "Partial rerun failed.", + ); + } + }} + /> +
+
+ + setEditorOpen(true)} + onPartialRerun={async () => { + if (!activeRunKey || !selectedStepId || !partialRerunState.enabled) { + setError(partialRerunState.reason || "Partial rerun is not available."); + return; + } + + try { + setError(""); + setFeedback(""); + await partialRerun(activeRunKey, selectedStepId); + setFeedback(`Partial rerun completed from step ${selectedStepId}.`); + } catch (nextError) { + setError( + nextError instanceof Error ? nextError.message : "Partial rerun failed.", + ); + } + }} + onSaveEdits={async () => { + if (!activeRunKey) { + return; + } + try { + setError(""); + setFeedback(""); + await saveRunEdits(activeRunKey); + setFeedback("Edits saved to the backend."); + } catch (nextError) { + setError(nextError instanceof Error ? nextError.message : "Save failed."); + } + }} + saveDisabled={saveDisabled} + /> +
+ + setEditorOpen(false)} + onApplyStructured={(stepId, nextStep) => { + if (!activeRunKey) { + return; + } + applyStructuredStepEdit(activeRunKey, stepId, nextStep); + setFeedback(`Applied structured changes to step ${stepId}.`); + }} + onApplyRaw={(input) => { + if (!activeRunKey) { + return; + } + applyRawJsonEdit(activeRunKey, input); + setFeedback("Applied raw pathway JSON changes."); + }} + /> + + {(isPartialRerunning || isSaving) && ( +
+
+

Working

+

{isPartialRerunning ? "Running partial synthesis" : "Saving pathway edits"}

+

The new viewer is waiting for the backend to finish.

+
+
+ )} +
+ ); +} diff --git a/viewer_new/src/config/loaders.ts b/viewer_new/src/config/loaders.ts new file mode 100644 index 00000000..77df98ce --- /dev/null +++ b/viewer_new/src/config/loaders.ts @@ -0,0 +1,20 @@ +import type { AdvancedSettingsConfig, ViewerRuntimeConfig } from "../types/viewer"; +import { advancedSettingsSchema, runtimeConfigSchema } from "../lib/schemas"; + +async function fetchJson(path: string): Promise { + const response = await fetch(path); + if (!response.ok) { + throw new Error(`Failed to load ${path}: ${response.status}`); + } + return response.json() as Promise; +} + +export async function loadRuntimeConfig() { + const data = await fetchJson("/runtime-config.json"); + return runtimeConfigSchema.parse(data); +} + +export async function loadAdvancedSettings() { + const data = await fetchJson("/advanced-settings.json"); + return advancedSettingsSchema.parse(data); +} diff --git a/viewer_new/src/config/storage.ts b/viewer_new/src/config/storage.ts new file mode 100644 index 00000000..4df0d6b1 --- /dev/null +++ b/viewer_new/src/config/storage.ts @@ -0,0 +1,34 @@ +const keys = { + apiKey: "deepretro.viewer.apiKey", + lastSmiles: "deepretro.viewer.lastSmiles", + activeRun: "deepretro.viewer.activeRun", +} as const; + +function read(key: string) { + if (typeof window === "undefined") { + return ""; + } + return window.localStorage.getItem(key) ?? ""; +} + +function write(key: string, value: string) { + if (typeof window === "undefined") { + return; + } + + if (value) { + window.localStorage.setItem(key, value); + } else { + window.localStorage.removeItem(key); + } +} + +export const storage = { + keys, + getApiKey: () => read(keys.apiKey), + setApiKey: (value: string) => write(keys.apiKey, value), + getLastSmiles: () => read(keys.lastSmiles), + setLastSmiles: (value: string) => write(keys.lastSmiles, value), + getActiveRun: () => read(keys.activeRun), + setActiveRun: (value: string) => write(keys.activeRun, value), +}; diff --git a/viewer_new/src/features/editor/EditorDrawer.tsx b/viewer_new/src/features/editor/EditorDrawer.tsx new file mode 100644 index 00000000..779d3fce --- /dev/null +++ b/viewer_new/src/features/editor/EditorDrawer.tsx @@ -0,0 +1,531 @@ +import { useEffect, useMemo, useState } from "react"; +import CodeMirror from "@uiw/react-codemirror"; +import { json as jsonLanguage } from "@codemirror/lang-json"; +import { X } from "lucide-react"; + +import { getStep, safeJsonParse, stringifyResult } from "../../lib/pathway"; +import type { MoleculeRecord, PathwayStep, ViewerRun } from "../../types/viewer"; + +type EditorDrawerProps = { + open: boolean; + run?: ViewerRun; + stepId?: string; + onClose: () => void; + onApplyStructured: (stepId: string, nextStep: PathwayStep) => void; + onApplyRaw: (input: unknown) => void; +}; + +function cloneStep(step: PathwayStep | undefined): PathwayStep { + if (!step) { + return { + step: "", + products: [], + reactants: [], + reagents: [], + conditions: {}, + reactionmetrics: [{}], + }; + } + + return JSON.parse(JSON.stringify(step)) as PathwayStep; +} + +function makeBlankMolecule(): MoleculeRecord { + return { + smiles: "", + product_metadata: {}, + }; +} + +function serializeConditionValue(value: unknown) { + if (typeof value === "string") { + return value; + } + if (value === undefined) { + return ""; + } + return JSON.stringify(value); +} + +function parseConditionValue(value: string) { + const trimmed = value.trim(); + if (!trimmed) { + return ""; + } + + const shouldParseAsJson = + trimmed.startsWith("{") || + trimmed.startsWith("[") || + trimmed.startsWith('"') || + trimmed === "true" || + trimmed === "false" || + trimmed === "null" || + /^-?\d+(\.\d+)?$/.test(trimmed); + + if (!shouldParseAsJson) { + return value; + } + + const parsed = safeJsonParse(trimmed); + if (parsed.data !== undefined) { + return parsed.data; + } + + return value; +} + +function ConditionEditor({ + conditions, + onChange, +}: { + conditions: PathwayStep["conditions"]; + onChange: (next: PathwayStep["conditions"]) => void; +}) { + const entries = Array.isArray(conditions) + ? [] + : Object.entries(conditions ?? {}).map(([key, value]) => ({ + key, + value: serializeConditionValue(value), + })); + + const updateEntries = ( + nextEntries: Array<{ + key: string; + value: string; + }>, + ) => { + const nextConditions: Record = {}; + nextEntries.forEach((entry) => { + if (!entry.key.trim()) { + return; + } + nextConditions[entry.key] = parseConditionValue(entry.value); + }); + onChange(nextConditions); + }; + + return ( +
+
+

Conditions

+ +
+ {Array.isArray(conditions) ? ( +

+ This step stores conditions as an array. Switch to raw JSON to edit that payload safely. +

+ ) : null} +
+ {entries.map((entry, index) => ( +
+ + + +
+ ))} + {!entries.length && !Array.isArray(conditions) ? ( +

No structured conditions yet. Add fields above.

+ ) : null} +
+
+ ); +} + +function MoleculeEditor({ + label, + molecules, + metadataKey, + onChange, +}: { + label: string; + molecules: MoleculeRecord[]; + metadataKey: "product_metadata" | "reactant_metadata" | "reagent_metadata"; + onChange: (next: MoleculeRecord[]) => void; +}) { + return ( +
+
+

{label}

+ +
+
+ {molecules.map((molecule, index) => { + const metadata = molecule[metadataKey] ?? {}; + return ( +
+ + + + + +
+ ); + })} +
+
+ ); +} + +export function EditorDrawer({ + open, + run, + stepId, + onClose, + onApplyStructured, + onApplyRaw, +}: EditorDrawerProps) { + const selectedStep = useMemo( + () => (run?.rawResult && stepId ? getStep(run.rawResult, stepId) : undefined), + [run?.rawResult, stepId], + ); + const [tab, setTab] = useState<"structured" | "raw">("structured"); + const [draft, setDraft] = useState(cloneStep(selectedStep)); + const [rawText, setRawText] = useState(stringifyResult(run?.rawResult)); + const [error, setError] = useState(""); + + useEffect(() => { + setDraft(cloneStep(selectedStep)); + }, [selectedStep]); + + useEffect(() => { + setRawText(stringifyResult(run?.rawResult)); + }, [run?.rawResult]); + + if (!open || !run || !stepId) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/viewer_new/src/features/graph/GraphCanvas.tsx b/viewer_new/src/features/graph/GraphCanvas.tsx new file mode 100644 index 00000000..0ddcec09 --- /dev/null +++ b/viewer_new/src/features/graph/GraphCanvas.tsx @@ -0,0 +1,278 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { + Background, + Controls, + type Edge, + type FitViewOptions, + MiniMap, + ReactFlow, + ReactFlowProvider, + useReactFlow, + type NodeTypes, +} from "@xyflow/react"; +import { Expand, Search } from "lucide-react"; + +import "@xyflow/react/dist/style.css"; + +import { buildFlowGraph, type StepFlowNode } from "../../lib/layout"; +import { StepNode } from "./StepNode"; +import type { ViewerRun } from "../../types/viewer"; + +const nodeTypes = { + step: StepNode, +} satisfies NodeTypes; + +type GraphCanvasProps = { + run?: ViewerRun; + selectedStepId?: string; + onSelectStep: (stepId: string) => void; + onEditStep: (stepId: string) => void; + onPartialRerunStep: (stepId: string) => void; +}; + +function GraphCanvasInner({ + run, + selectedStepId, + onSelectStep, + onEditStep, + onPartialRerunStep, +}: GraphCanvasProps) { + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [search, setSearch] = useState(""); + const shellRef = useRef(null); + const handlersRef = useRef({ + onSelectStep, + onEditStep, + onPartialRerunStep, + }); + const lastFitSignatureRef = useRef(""); + const reactFlow = useReactFlow(); + + useEffect(() => { + handlersRef.current = { + onSelectStep, + onEditStep, + onPartialRerunStep, + }; + }, [onEditStep, onPartialRerunStep, onSelectStep]); + + const searchQuery = search.trim().toLowerCase(); + + const matchesSearch = useMemo(() => { + return (stepNode: NonNullable["nodes"][number]) => { + if (!searchQuery) { + return true; + } + + const haystacks = [ + stepNode.title, + ...stepNode.products.map((molecule) => `${molecule.label} ${molecule.smiles}`), + ...stepNode.reactants.map((molecule) => `${molecule.label} ${molecule.smiles}`), + ...stepNode.reagents.map((molecule) => `${molecule.label} ${molecule.smiles}`), + ]; + + return haystacks.some((value) => value.toLowerCase().includes(searchQuery)); + }; + }, [searchQuery]); + + const visibleNodeIds = useMemo(() => { + if (!searchQuery) { + return new Set(nodes.map((node) => node.id)); + } + + return new Set( + nodes + .filter((node) => matchesSearch(node.data.stepNode)) + .map((node) => node.id), + ); + }, [matchesSearch, nodes, searchQuery]); + + useEffect(() => { + let cancelled = false; + + if (!run?.graph) { + setNodes([]); + setEdges([]); + return () => { + cancelled = true; + }; + } + + buildFlowGraph(run.graph, { + onInspect: (stepId) => handlersRef.current.onSelectStep(stepId), + onEdit: (stepId) => handlersRef.current.onEditStep(stepId), + onPartialRerun: (stepId) => handlersRef.current.onPartialRerunStep(stepId), + matchesSearch: () => true, + }).then((flow) => { + if (!cancelled) { + setNodes(flow.nodes); + setEdges(flow.edges); + } + }); + + return () => { + cancelled = true; + }; + }, [run?.graph]); + + useEffect(() => { + if (!nodes.length) { + return; + } + + const signature = `${run?.key ?? "no-run"}:${searchQuery}:${Array.from(visibleNodeIds) + .sort() + .join(",")}`; + if (signature === lastFitSignatureRef.current) { + return; + } + lastFitSignatureRef.current = signature; + + const targetNodes = searchQuery + ? nodes.filter((node) => visibleNodeIds.has(node.id)) + : nodes; + + const timeout = window.setTimeout(() => { + const options: FitViewOptions = { + duration: 350, + padding: searchQuery ? 0.08 : 0.14, + includeHiddenNodes: true, + }; + if (searchQuery) { + options.minZoom = 0.82; + } + if (targetNodes.length) { + options.nodes = targetNodes; + } + reactFlow.fitView(options); + }, 40); + + return () => { + window.clearTimeout(timeout); + }; + }, [nodes, reactFlow, searchQuery, visibleNodeIds]); + + useEffect(() => { + if (!searchQuery) { + return; + } + const firstMatch = nodes.find((node) => visibleNodeIds.has(node.id)); + if (firstMatch) { + onSelectStep(firstMatch.data.stepNode.stepId); + } + }, [nodes, onSelectStep, searchQuery, visibleNodeIds]); + + if (!run) { + return ( +
+

No pathway selected

+

Run an analysis or upload a pathway JSON to populate the canvas.

+
+ ); + } + + if (run.status === "error") { + return ( +
+

Pathway request failed

+

{run.error}

+
+ ); + } + + if (!run.graph) { + return ( +
+

Waiting for graph data

+

The active run has not produced a pathway graph yet.

+
+ ); + } + + return ( +
+
+ + +
+
+ ({ + ...node, + hidden: !visibleNodeIds.has(node.id), + selected: node.data.stepNode.stepId === selectedStepId, + data: { + ...node.data, + highlighted: visibleNodeIds.has(node.id), + }, + }))} + edges={edges.map((edge) => ({ + ...edge, + hidden: + !visibleNodeIds.has(edge.source) || !visibleNodeIds.has(edge.target), + }))} + nodeTypes={nodeTypes} + minZoom={0.35} + maxZoom={2.2} + defaultEdgeOptions={{ + style: { + stroke: "#ffffff", + strokeWidth: 4, + }, + }} + onNodeClick={(_, node) => onSelectStep(node.data.stepNode.stepId)} + proOptions={{ hideAttribution: true }} + > + + node.id === "step-0" ? "#7ad2ac" : node.selected ? "#ff8663" : "#8fa7ca" + } + /> + + + +
+
+ ); +} + +export function GraphCanvas(props: GraphCanvasProps) { + return ( + + + + ); +} diff --git a/viewer_new/src/features/graph/MoleculePreview.tsx b/viewer_new/src/features/graph/MoleculePreview.tsx new file mode 100644 index 00000000..2b654614 --- /dev/null +++ b/viewer_new/src/features/graph/MoleculePreview.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; + +import { renderMoleculeSvg } from "../../lib/rdkit"; + +type MoleculePreviewProps = { + smiles: string; + label: string; + width?: number; + height?: number; + compact?: boolean; +}; + +export function MoleculePreview({ + smiles, + label, + width = 180, + height = 120, + compact = false, +}: MoleculePreviewProps) { + const [svg, setSvg] = useState(""); + const [error, setError] = useState(""); + + useEffect(() => { + let cancelled = false; + setSvg(""); + setError(""); + + if (!smiles) { + setError("Missing SMILES"); + return () => { + cancelled = true; + }; + } + + renderMoleculeSvg(smiles, width, height) + .then((nextSvg) => { + if (!cancelled) { + setSvg(nextSvg); + } + }) + .catch((nextError) => { + if (!cancelled) { + setError(nextError instanceof Error ? nextError.message : "Render failed"); + } + }); + + return () => { + cancelled = true; + }; + }, [height, smiles, width]); + + if (error) { + return ( +
+ {label} + {smiles} + {error} +
+ ); + } + + if (!svg) { + return ( +
+ Rendering... +
+ ); + } + + return ( +
+ ); +} diff --git a/viewer_new/src/features/graph/StepNode.tsx b/viewer_new/src/features/graph/StepNode.tsx new file mode 100644 index 00000000..4bc64841 --- /dev/null +++ b/viewer_new/src/features/graph/StepNode.tsx @@ -0,0 +1,126 @@ +import { Handle, Position, type NodeProps } from "@xyflow/react"; +import { Beaker, FlaskConical, Pencil, RefreshCcw } from "lucide-react"; + +import { formatConfidence } from "../../lib/pathway"; +import { MoleculePreview } from "./MoleculePreview"; +import type { StepFlowNode } from "../../lib/layout"; + +export function StepNode({ + data, + selected, +}: NodeProps) { + const stepNode = data.stepNode; + const product = stepNode.products[0]; + const primaryFormula = product?.formula ?? product?.label; + + return ( +
data.onInspect(stepNode.stepId)} + role="button" + tabIndex={0} + style={{ + width: `${data.cardWidth}px`, + minHeight: `${data.cardHeight}px`, + }} + > + + + + {product ? ( +
+ +
+ ) : ( +
No product data available
+ )} + +
+
+
+

{stepNode.isVirtualRoot ? "Target" : "Synthesis step"}

+

{stepNode.isVirtualRoot ? "Target" : `Step ${stepNode.stepId}`}

+
+
+ {stepNode.metrics?.confidenceestimate !== undefined ? ( + + + {formatConfidence(stepNode.metrics.confidenceestimate)} + + ) : null} + {stepNode.metrics?.scalabilityindex !== undefined ? ( + + + Scale {stepNode.metrics.scalabilityindex} + + ) : null} +
+
+ +
+ {primaryFormula ? ( + {primaryFormula} + ) : null} + {stepNode.products.length} product + {stepNode.reactants.length} reactants + {stepNode.reagents.length ? ( + {stepNode.reagents.length} reagents + ) : null} +
+ + {!stepNode.isVirtualRoot ? ( +
+ + + +
+ ) : null} +
+
+ ); +} diff --git a/viewer_new/src/features/inspector/InspectorPanel.tsx b/viewer_new/src/features/inspector/InspectorPanel.tsx new file mode 100644 index 00000000..260519e1 --- /dev/null +++ b/viewer_new/src/features/inspector/InspectorPanel.tsx @@ -0,0 +1,256 @@ +import { ChevronDown, Pencil, RefreshCcw, Save } from "lucide-react"; + +import { + canPartialRerunStep, + formatConfidence, + getStep, + stringifyResult, +} from "../../lib/pathway"; +import { MoleculePreview } from "../graph/MoleculePreview"; +import type { ViewerRun } from "../../types/viewer"; + +type InspectorPanelProps = { + run?: ViewerRun; + selectedStepId?: string; + onSelectStep: (stepId: string) => void; + onEdit: () => void; + onPartialRerun: () => void; + onSaveEdits: () => void; + saveDisabled: boolean; +}; + +function MoleculeSection({ + title, + molecules, +}: { + title: string; + molecules: NonNullable["nodes"][number]["products"]>; +}) { + if (!molecules.length) { + return null; + } + + return ( +
+
+

{title}

+ {molecules.length} +
+
+ {molecules.map((molecule) => ( +
+ + {molecule.label} +

{molecule.smiles}

+
+ {molecule.formula ? {molecule.formula} : null} + {typeof molecule.mass === "number" ? ( + {molecule.mass.toFixed(1)} g/mol + ) : null} +
+
+ ))} +
+
+ ); +} + +function ConditionCards({ + conditions, +}: { + conditions: Record | unknown[] | undefined; +}) { + if (!conditions || Array.isArray(conditions)) { + return ( +
+
+ Conditions + {conditions ? "Unstructured payload" : "Not provided"} +
+
+ ); + } + + const entries = Object.entries(conditions); + if (!entries.length) { + return ( +
+
+ Conditions + Not provided +
+
+ ); + } + + return ( +
+ {entries.map(([key, value]) => ( +
+ {key.replaceAll("_", " ")} + {typeof value === "string" ? value : JSON.stringify(value)} +
+ ))} +
+ ); +} + +export function InspectorPanel({ + run, + selectedStepId, + onSelectStep, + onEdit, + onPartialRerun, + onSaveEdits, + saveDisabled, +}: InspectorPanelProps) { + if (!run?.graph) { + return ( + + ); + } + + const activeStepId = selectedStepId ?? run.graph.virtualRoot.stepId; + const stepNode = run.graph.nodes.find((node) => node.stepId === activeStepId); + const rawStep = getStep(run.rawResult, activeStepId); + const rerunState = canPartialRerunStep(rawStep); + + return ( + + ); +} diff --git a/viewer_new/src/features/runs/RunSidebar.tsx b/viewer_new/src/features/runs/RunSidebar.tsx new file mode 100644 index 00000000..9ed9055c --- /dev/null +++ b/viewer_new/src/features/runs/RunSidebar.tsx @@ -0,0 +1,285 @@ +import { useId } from "react"; +import { Activity, CheckCircle2, CircleAlert, LoaderCircle, Upload } from "lucide-react"; + +import type { + AdvancedSettingsConfig, + HealthStatusMap, + InstanceRequestSettings, + ViewerRun, + ViewerRuntimeConfig, +} from "../../types/viewer"; + +type RunSidebarProps = { + runtimeConfig?: ViewerRuntimeConfig; + advancedSettings?: AdvancedSettingsConfig; + health: HealthStatusMap; + instanceSettings: Record; + runs: Record; + activeRunKey?: string; + onRunSelect: (runKey: string) => void; + onUploadFiles: (files: Array<{ fileName: string; input: unknown }>) => void; + onUpdateSetting: ( + instanceId: string, + patch: Partial, + ) => void; +}; + +function StatusIcon({ run }: { run?: ViewerRun }) { + if (!run) { + return ; + } + if (run.status === "pending") { + return ; + } + if (run.status === "success") { + return ; + } + if (run.status === "error") { + return ; + } + return ; +} + +function ToggleRow({ + label, + checked, + disabled, + onChange, +}: { + label: string; + checked: boolean; + disabled?: boolean; + onChange: (checked: boolean) => void; +}) { + return ( + + ); +} + +export function RunSidebar({ + runtimeConfig, + advancedSettings, + health, + instanceSettings, + runs, + activeRunKey, + onRunSelect, + onUploadFiles, + onUpdateSetting, +}: RunSidebarProps) { + const uploadInputId = useId(); + const uploadedRuns = Object.values(runs).filter((run) => run.source === "file"); + + return ( + + ); +} diff --git a/viewer_new/src/features/runs/store.ts b/viewer_new/src/features/runs/store.ts new file mode 100644 index 00000000..640ec24f --- /dev/null +++ b/viewer_new/src/features/runs/store.ts @@ -0,0 +1,96 @@ +import { create } from "zustand"; + +import { storage } from "../../config/storage"; +import type { + AdvancedSettingsConfig, + HealthStatusMap, + InstanceRequestSettings, + ViewerMode, + ViewerRun, + ViewerRuntimeConfig, + ViewerStoreState, +} from "../../types/viewer"; + +type ViewerActions = { + bootstrap: (payload: { + runtimeConfig: ViewerRuntimeConfig; + advancedSettings: AdvancedSettingsConfig; + }) => void; + setMode: (mode: ViewerMode) => void; + setApiKey: (apiKey: string) => void; + setCurrentSmiles: (smiles: string) => void; + setHealth: (health: HealthStatusMap) => void; + setActiveRun: (runKey?: string) => void; + setSelectedStep: (stepId?: string) => void; + setRuns: ( + updater: (runs: Record) => Record, + ) => void; + updateInstanceSetting: ( + instanceId: string, + patch: Partial, + ) => void; + setEditorOpen: (open: boolean) => void; +}; + +type ViewerStore = ViewerStoreState & ViewerActions; + +export const useViewerStore = create((set, get) => ({ + mode: "analyze", + apiKey: storage.getApiKey(), + currentSmiles: storage.getLastSmiles(), + activeRunKey: storage.getActiveRun() || undefined, + selectedStepId: undefined, + runs: {}, + instanceSettings: {}, + health: {}, + editorOpen: false, + bootstrap: ({ runtimeConfig, advancedSettings }) => { + const existing = get().instanceSettings; + const nextSettings = Object.fromEntries( + runtimeConfig.instances.map((instance) => [ + instance.id, + existing[instance.id] ?? instance.defaults, + ]), + ); + + set({ + runtimeConfig, + advancedSettings, + instanceSettings: nextSettings, + }); + }, + setMode: (mode) => set({ mode }), + setApiKey: (apiKey) => { + storage.setApiKey(apiKey); + set({ apiKey }); + }, + setCurrentSmiles: (smiles) => { + storage.setLastSmiles(smiles); + set({ currentSmiles: smiles }); + }, + setHealth: (health) => set({ health }), + setActiveRun: (runKey) => { + storage.setActiveRun(runKey ?? ""); + const nextRun = runKey ? get().runs[runKey] : undefined; + const nextStepId = + nextRun?.graph?.nodes.find((node) => !node.isVirtualRoot)?.stepId ?? + nextRun?.graph?.virtualRoot.stepId; + set({ + activeRunKey: runKey, + selectedStepId: nextStepId, + }); + }, + setSelectedStep: (stepId) => set({ selectedStepId: stepId }), + setRuns: (updater) => set((state) => ({ runs: updater(state.runs) })), + updateInstanceSetting: (instanceId, patch) => + set((state) => ({ + instanceSettings: { + ...state.instanceSettings, + [instanceId]: { + ...state.instanceSettings[instanceId], + ...patch, + }, + }, + })), + setEditorOpen: (editorOpen) => set({ editorOpen }), +})); diff --git a/viewer_new/src/features/runs/useViewerActions.ts b/viewer_new/src/features/runs/useViewerActions.ts new file mode 100644 index 00000000..810745da --- /dev/null +++ b/viewer_new/src/features/runs/useViewerActions.ts @@ -0,0 +1,464 @@ +import { useCallback, useEffect, useRef } from "react"; +import { useMutation } from "@tanstack/react-query"; + +import { loadAdvancedSettings, loadRuntimeConfig } from "../../config/loaders"; +import { createFlaskClient } from "../../api/flaskClient"; +import { storage } from "../../config/storage"; +import { + buildPathwayGraph, + canPartialRerunStep, + getStep, + inferTopLevelSmiles, + updateStepInResult, + validatePathwayResult, + validatePathwayStep, +} from "../../lib/pathway"; +import { useViewerStore } from "./store"; +import type { + AnalysisRequest, + HealthStatusMap, + ViewerRun, +} from "../../types/viewer"; + +function createRun( + key: string, + label: string, + status: ViewerRun["status"], + source: ViewerRun["source"], + payload: Partial, +): ViewerRun { + return { + key, + label, + source, + status, + dirty: false, + lastUpdatedAt: Date.now(), + ...payload, + }; +} + +export function useBootstrapViewer() { + const bootstrap = useViewerStore((state) => state.bootstrap); + + useEffect(() => { + let cancelled = false; + + Promise.all([loadRuntimeConfig(), loadAdvancedSettings()]) + .then(([runtimeConfig, advancedSettings]) => { + if (!cancelled) { + bootstrap({ runtimeConfig, advancedSettings }); + } + }) + .catch((error) => { + console.error("Failed to bootstrap viewer:", error); + }); + + return () => { + cancelled = true; + }; + }, [bootstrap]); +} + +export function useViewerActions() { + const runtimeConfig = useViewerStore((state) => state.runtimeConfig); + const instanceSettings = useViewerStore((state) => state.instanceSettings); + const apiKey = useViewerStore((state) => state.apiKey); + const runs = useViewerStore((state) => state.runs); + const activeRunKey = useViewerStore((state) => state.activeRunKey); + const setRuns = useViewerStore((state) => state.setRuns); + const setHealth = useViewerStore((state) => state.setHealth); + const setActiveRun = useViewerStore((state) => state.setActiveRun); + const setSelectedStep = useViewerStore((state) => state.setSelectedStep); + const healthAttemptRef = useRef(0); + const healthTimeoutRef = useRef(null); + + const runHealthCheck = useCallback(async () => { + if (!runtimeConfig) { + return {} as HealthStatusMap; + } + + const statusMap: HealthStatusMap = {}; + + if (!apiKey) { + for (const instance of runtimeConfig.instances) { + statusMap[instance.id] = { + state: "unauthorized", + message: "API key required", + }; + } + setHealth(statusMap); + return statusMap; + } + + await Promise.all( + runtimeConfig.instances.map(async (instance) => { + const client = createFlaskClient({ + baseUrl: instance.baseUrl, + endpoints: runtimeConfig.endpoints, + apiKey, + }); + + try { + await client.health(); + statusMap[instance.id] = { + state: "healthy", + message: "Healthy", + }; + } catch (error) { + const message = error instanceof Error ? error.message : "Health check failed"; + statusMap[instance.id] = { + state: message.toLowerCase().includes("unauthorized") + ? "unauthorized" + : "error", + message, + }; + } + }), + ); + + setHealth(statusMap); + return statusMap; + }, [apiKey, runtimeConfig, setHealth]); + + const healthMutation = useMutation({ + mutationFn: runHealthCheck, + }); + + const analyzeMutation = useMutation({ + mutationFn: async ({ + smiles, + rerun, + }: { + smiles: string; + rerun: boolean; + }) => { + if (!runtimeConfig) { + throw new Error("Runtime config has not loaded yet."); + } + + if (!apiKey) { + throw new Error("An API key is required before running the viewer."); + } + + const pendingRuns = runtimeConfig.instances.reduce>( + (accumulator, instance) => { + const key = `api:${instance.id}`; + accumulator[key] = createRun(key, instance.label, "pending", "api", { + instanceId: instance.id, + request: { + smiles, + ...instanceSettings[instance.id], + }, + }); + return accumulator; + }, + {}, + ); + + setRuns((previous) => ({ + ...previous, + ...pendingRuns, + })); + + const results = await Promise.all( + runtimeConfig.instances.map(async (instance) => { + const request: AnalysisRequest = { + smiles, + ...instanceSettings[instance.id], + }; + + const startedAt = performance.now(); + const client = createFlaskClient({ + baseUrl: instance.baseUrl, + endpoints: runtimeConfig.endpoints, + apiKey, + }); + + try { + const rawResult = rerun + ? await client.rerun(request) + : await client.retrosynthesis(request); + return { + key: `api:${instance.id}`, + run: createRun(`api:${instance.id}`, instance.label, "success", "api", { + instanceId: instance.id, + request, + rawResult, + graph: buildPathwayGraph(rawResult), + durationMs: Math.round(performance.now() - startedAt), + }), + }; + } catch (error) { + return { + key: `api:${instance.id}`, + run: createRun(`api:${instance.id}`, instance.label, "error", "api", { + instanceId: instance.id, + request, + error: error instanceof Error ? error.message : "Request failed", + durationMs: Math.round(performance.now() - startedAt), + }), + }; + } + }), + ); + + return { smiles, results }; + }, + onSuccess: ({ smiles, results }) => { + storage.setLastSmiles(smiles); + setRuns((previous) => { + const nextRuns = { ...previous }; + for (const { key, run } of results) { + nextRuns[key] = run; + } + return nextRuns; + }); + + const firstSuccessfulRun = results.find((item) => item.run.status === "success"); + setActiveRun(firstSuccessfulRun?.key ?? results[0]?.key); + setSelectedStep("0"); + void healthMutation.mutateAsync(); + }, + }); + + const saveMutation = useMutation({ + mutationFn: async (runKey: string) => { + if (!runtimeConfig) { + throw new Error("Runtime config has not loaded yet."); + } + + const run = runs[runKey]; + if (!run?.instanceId || !run.rawResult || !run.request) { + throw new Error("Only API runs with loaded results can be saved."); + } + + const instance = runtimeConfig.instances.find( + (candidate) => candidate.id === run.instanceId, + ); + if (!instance) { + throw new Error("Missing backend instance configuration."); + } + + const client = createFlaskClient({ + baseUrl: instance.baseUrl, + endpoints: runtimeConfig.endpoints, + apiKey, + }); + + const smiles = run.request.smiles || inferTopLevelSmiles(run.rawResult); + await client.saveEditedResult(smiles, run.rawResult); + return runKey; + }, + onSuccess: (runKey) => { + setRuns((previous) => ({ + ...previous, + [runKey]: { + ...previous[runKey], + dirty: false, + lastUpdatedAt: Date.now(), + }, + })); + }, + }); + + const partialRerunMutation = useMutation({ + mutationFn: async ({ + runKey, + stepId, + }: { + runKey: string; + stepId: string; + }) => { + if (!runtimeConfig) { + throw new Error("Runtime config has not loaded yet."); + } + + const run = runs[runKey]; + if (!run?.instanceId || !run.request || !run.rawResult) { + throw new Error("Select an API run before triggering partial rerun."); + } + + const step = getStep(run.rawResult, stepId); + const eligibility = canPartialRerunStep(step); + if (!eligibility.enabled) { + throw new Error(eligibility.reason); + } + + const instance = runtimeConfig.instances.find( + (candidate) => candidate.id === run.instanceId, + ); + if (!instance) { + throw new Error("Missing backend instance configuration."); + } + + const client = createFlaskClient({ + baseUrl: instance.baseUrl, + endpoints: runtimeConfig.endpoints, + apiKey, + }); + + const request = { + smiles: run.request.smiles, + steps: stepId, + ...instanceSettings[run.instanceId], + }; + + const rawResult = await client.partialRerun(request); + return { runKey, rawResult }; + }, + onSuccess: ({ runKey, rawResult }) => { + const currentRun = runs[runKey]; + setRuns((previous) => ({ + ...previous, + [runKey]: { + ...previous[runKey], + rawResult, + graph: buildPathwayGraph(rawResult), + dirty: false, + error: undefined, + status: "success", + lastUpdatedAt: Date.now(), + request: currentRun?.request + ? { + ...currentRun.request, + smiles: currentRun.request.smiles, + } + : currentRun?.request, + }, + })); + setActiveRun(runKey); + setSelectedStep("0"); + }, + }); + + useEffect(() => { + if (!runtimeConfig) { + return; + } + + if (healthTimeoutRef.current) { + window.clearTimeout(healthTimeoutRef.current); + healthTimeoutRef.current = null; + } + + if (!apiKey) { + void runHealthCheck(); + return; + } + + let cancelled = false; + + const scheduleNext = (delayMs: number) => { + if (cancelled) { + return; + } + + healthTimeoutRef.current = window.setTimeout(async () => { + const statusMap = await runHealthCheck(); + const hasRecoverableError = Object.values(statusMap).some( + (status) => status.state === "error", + ); + + healthAttemptRef.current = hasRecoverableError + ? healthAttemptRef.current + 1 + : 0; + + const nextDelay = hasRecoverableError + ? Math.min(30_000, 1_000 * 2 ** healthAttemptRef.current) + : 15_000; + + scheduleNext(nextDelay); + }, delayMs); + }; + + healthAttemptRef.current = 0; + scheduleNext(0); + + return () => { + cancelled = true; + if (healthTimeoutRef.current) { + window.clearTimeout(healthTimeoutRef.current); + healthTimeoutRef.current = null; + } + }; + }, [apiKey, runHealthCheck, runtimeConfig]); + + return { + runtimeConfig, + activeRun: activeRunKey ? runs[activeRunKey] : undefined, + activeRunKey, + isAnalyzing: analyzeMutation.isPending, + isSaving: saveMutation.isPending, + isPartialRerunning: partialRerunMutation.isPending, + refreshHealth: () => healthMutation.mutateAsync(), + analyzeAll: (smiles: string, rerun = false) => + analyzeMutation.mutateAsync({ smiles, rerun }), + loadUploadedResult: (fileName: string, input: unknown) => { + const validation = validatePathwayResult(input); + if (!validation.data) { + throw new Error(validation.issues.map((issue) => issue.message).join("\n")); + } + + const key = `file:${fileName}`; + const run = createRun(key, fileName, "success", "file", { + rawResult: validation.data, + graph: buildPathwayGraph(validation.data), + }); + + setRuns((previous) => ({ + ...previous, + [key]: run, + })); + setActiveRun(key); + setSelectedStep("0"); + return key; + }, + applyStructuredStepEdit: (runKey: string, stepId: string, nextStep: unknown) => { + const validation = validatePathwayStep(nextStep); + if (!validation.data) { + throw new Error(validation.issues.map((issue) => issue.message).join("\n")); + } + + const run = runs[runKey]; + if (!run?.rawResult) { + throw new Error("No run data is loaded."); + } + + const rawResult = updateStepInResult(run.rawResult, stepId, validation.data); + const graph = buildPathwayGraph(rawResult); + + setRuns((previous) => ({ + ...previous, + [runKey]: { + ...previous[runKey], + rawResult, + graph, + dirty: true, + lastUpdatedAt: Date.now(), + }, + })); + }, + applyRawJsonEdit: (runKey: string, input: unknown) => { + const validation = validatePathwayResult(input); + if (!validation.data) { + throw new Error(validation.issues.map((issue) => issue.message).join("\n")); + } + + setRuns((previous) => ({ + ...previous, + [runKey]: { + ...previous[runKey], + rawResult: validation.data, + graph: buildPathwayGraph(validation.data), + dirty: true, + lastUpdatedAt: Date.now(), + }, + })); + setSelectedStep("0"); + }, + saveRunEdits: (runKey: string) => saveMutation.mutateAsync(runKey), + partialRerun: (runKey: string, stepId: string) => + partialRerunMutation.mutateAsync({ runKey, stepId }), + }; +} diff --git a/viewer_new/src/features/upload/UploadPanel.test.tsx b/viewer_new/src/features/upload/UploadPanel.test.tsx new file mode 100644 index 00000000..a34ba602 --- /dev/null +++ b/viewer_new/src/features/upload/UploadPanel.test.tsx @@ -0,0 +1,52 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, test, vi } from "vitest"; + +import { UploadPanel } from "./UploadPanel"; + +describe("UploadPanel", () => { + test("parses a valid JSON upload and forwards it to the callback", async () => { + const onLoad = vi.fn(); + const user = userEvent.setup(); + + render( + { + files.forEach(({ fileName, input }) => onLoad(fileName, input)); + }} + />, + ); + + const input = screen.getByLabelText(/choose one or more `.json` pathway exports/i); + const file = new File([JSON.stringify({ steps: [] })], "pathway.json", { + type: "application/json", + }); + + await user.upload(input, file); + + expect(onLoad).toHaveBeenCalledWith("pathway.json", { steps: [] }); + }); + + test("surfaces a parsing error for malformed JSON", async () => { + const onLoad = vi.fn(); + const user = userEvent.setup(); + + render( + { + files.forEach(({ fileName, input }) => onLoad(fileName, input)); + }} + />, + ); + + const input = screen.getByLabelText(/choose one or more `.json` pathway exports/i); + const file = new File(["not-json"], "broken.json", { + type: "application/json", + }); + + await user.upload(input, file); + + expect(onLoad).not.toHaveBeenCalled(); + expect(screen.getByText(/unexpected token/i)).toBeInTheDocument(); + }); +}); diff --git a/viewer_new/src/features/upload/UploadPanel.tsx b/viewer_new/src/features/upload/UploadPanel.tsx new file mode 100644 index 00000000..5f036625 --- /dev/null +++ b/viewer_new/src/features/upload/UploadPanel.tsx @@ -0,0 +1,60 @@ +import { useId, useState } from "react"; +import { FileUp } from "lucide-react"; + +type UploadPanelProps = { + onLoadFiles: (files: Array<{ fileName: string; input: unknown }>) => void; +}; + +export function UploadPanel({ onLoadFiles }: UploadPanelProps) { + const inputId = useId(); + const [error, setError] = useState(""); + + return ( +
+
+
+

Upload mode

+

Inspect existing pathway JSON

+
+
+ + { + const files = Array.from(event.target.files ?? []); + if (!files.length) { + return; + } + + try { + setError(""); + const parsedFiles = await Promise.all( + files.map(async (file) => ({ + fileName: file.name, + input: JSON.parse(await file.text()), + })), + ); + onLoadFiles(parsedFiles); + } catch (nextError) { + setError( + nextError instanceof Error ? nextError.message : "Failed to load the file.", + ); + } finally { + event.target.value = ""; + } + }} + /> + {error ?

{error}

: null} +
+ ); +} diff --git a/viewer_new/src/lib/layout.ts b/viewer_new/src/lib/layout.ts new file mode 100644 index 00000000..036c3004 --- /dev/null +++ b/viewer_new/src/lib/layout.ts @@ -0,0 +1,227 @@ +import ELK from "elkjs/lib/elk.bundled.js"; +import type { Edge, Node } from "@xyflow/react"; +import { MarkerType, Position } from "@xyflow/react"; + +import type { + NormalizedPathwayGraph, + NormalizedStepNode, +} from "../types/viewer"; + +export type StepFlowNode = Node<{ + stepNode: NormalizedStepNode; + highlighted: boolean; + cardWidth: number; + cardHeight: number; + previewWidth: number; + previewHeight: number; + onInspect: (stepId: string) => void; + onEdit: (stepId: string) => void; + onPartialRerun: (stepId: string) => void; +}>; + +const elk = new ELK(); + +function getMoleculeSizing(stepNode: NormalizedStepNode) { + const smiles = stepNode.products[0]?.smiles ?? ""; + const atomCount = (smiles.match(/[A-Z][a-z]?/g) ?? []).length; + const complexity = Math.max(smiles.length, atomCount * 4); + + if (stepNode.isVirtualRoot) { + if (complexity > 110) { + return { + cardWidth: 500, + cardHeight: 460, + previewWidth: 420, + previewHeight: 380, + }; + } + + return { + cardWidth: 430, + cardHeight: 390, + previewWidth: 360, + previewHeight: 320, + }; + } + + if (complexity > 110) { + return { + cardWidth: 430, + cardHeight: 390, + previewWidth: 360, + previewHeight: 320, + }; + } + + if (complexity > 75) { + return { + cardWidth: 370, + cardHeight: 340, + previewWidth: 310, + previewHeight: 275, + }; + } + + return { + cardWidth: stepNode.isVirtualRoot ? 430 : 290, + cardHeight: stepNode.isVirtualRoot ? 390 : 290, + previewWidth: stepNode.isVirtualRoot ? 360 : 235, + previewHeight: stepNode.isVirtualRoot ? 320 : 210, + }; +} + +function toFlowEdges(graph: NormalizedPathwayGraph) { + return graph.edges.map((edge) => ({ + ...edge, + type: "smoothstep" as const, + animated: false, + markerEnd: { + type: MarkerType.ArrowClosed, + width: 24, + height: 24, + color: "#ffffff", + }, + style: { + stroke: "#ffffff", + strokeOpacity: 1, + strokeWidth: 4, + filter: "drop-shadow(0 0 10px rgba(255, 255, 255, 0.55))", + }, + })); +} + +function buildFallbackFlowGraph( + graph: NormalizedPathwayGraph, + options: { + onInspect: (stepId: string) => void; + onEdit: (stepId: string) => void; + onPartialRerun: (stepId: string) => void; + matchesSearch: (stepNode: NormalizedStepNode) => boolean; + }, +) { + const depthMap = new Map([["step-0", 0]]); + const queue = ["step-0"]; + + while (queue.length) { + const currentId = queue.shift()!; + const currentDepth = depthMap.get(currentId) ?? 0; + for (const edge of graph.edges) { + if (edge.source !== currentId) { + continue; + } + if (!depthMap.has(edge.target)) { + depthMap.set(edge.target, currentDepth + 1); + queue.push(edge.target); + } + } + } + + const rowsByDepth = new Map(); + + return { + nodes: graph.nodes.map((stepNode) => { + const depth = depthMap.get(stepNode.id) ?? 0; + const row = rowsByDepth.get(depth) ?? 0; + rowsByDepth.set(depth, row + 1); + const sizing = getMoleculeSizing(stepNode); + + return { + id: stepNode.id, + type: "step", + position: { + x: depth * 420, + y: row * 280, + }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + data: { + stepNode, + highlighted: options.matchesSearch(stepNode), + ...sizing, + onInspect: options.onInspect, + onEdit: options.onEdit, + onPartialRerun: options.onPartialRerun, + }, + draggable: false, + }; + }), + edges: toFlowEdges(graph), + }; +} + +export async function buildFlowGraph( + graph: NormalizedPathwayGraph, + options: { + onInspect: (stepId: string) => void; + onEdit: (stepId: string) => void; + onPartialRerun: (stepId: string) => void; + matchesSearch: (stepNode: NormalizedStepNode) => boolean; + }, +): Promise<{ + nodes: StepFlowNode[]; + edges: Edge[]; +}> { + try { + const elkGraph = await elk.layout({ + id: "deepretro-pathway", + layoutOptions: { + "elk.algorithm": "layered", + "elk.direction": "RIGHT", + "elk.layered.spacing.nodeNodeBetweenLayers": "280", + "elk.spacing.nodeNode": "210", + "elk.padding": "[top=72,left=72,bottom=72,right=140]", + }, + children: graph.nodes.map((stepNode) => { + const sizing = getMoleculeSizing(stepNode); + return { + id: stepNode.id, + width: sizing.cardWidth, + height: sizing.cardHeight, + }; + }), + edges: graph.edges.map((edge) => ({ + id: edge.id, + sources: [edge.source], + targets: [edge.target], + })), + }); + + const layoutNodes = new Map( + (elkGraph.children ?? []).map((child) => [child.id, child]), + ); + + if (!layoutNodes.size) { + return buildFallbackFlowGraph(graph, options); + } + + return { + nodes: graph.nodes.map((stepNode) => { + const layoutNode = layoutNodes.get(stepNode.id); + const sizing = getMoleculeSizing(stepNode); + return { + id: stepNode.id, + type: "step", + position: { + x: layoutNode?.x ?? 0, + y: layoutNode?.y ?? 0, + }, + sourcePosition: Position.Right, + targetPosition: Position.Left, + data: { + stepNode, + highlighted: options.matchesSearch(stepNode), + ...sizing, + onInspect: options.onInspect, + onEdit: options.onEdit, + onPartialRerun: options.onPartialRerun, + }, + draggable: false, + }; + }), + edges: toFlowEdges(graph), + }; + } catch (error) { + console.error("ELK layout failed, using fallback placement.", error); + return buildFallbackFlowGraph(graph, options); + } +} diff --git a/viewer_new/src/lib/pathway.test.ts b/viewer_new/src/lib/pathway.test.ts new file mode 100644 index 00000000..d2b3c03d --- /dev/null +++ b/viewer_new/src/lib/pathway.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, test } from "vitest"; + +import { + buildPathwayGraph, + canPartialRerunStep, + updateStepInResult, + validatePathwayResult, +} from "./pathway"; +import type { PathwayResult } from "../types/viewer"; + +const sampleResult: PathwayResult = { + dependencies: { + 1: ["2"], + 2: [], + }, + steps: [ + { + step: "1", + reactants: [{ smiles: "CCO", reactant_metadata: { chemical_formula: "C2H6O" } }], + products: [{ smiles: "CC=O", product_metadata: { chemical_formula: "C2H4O" } }], + reactionmetrics: [{ confidenceestimate: 0.88, scalabilityindex: "7" }], + conditions: { solvent: "MeOH" }, + }, + { + step: "2", + reactants: [{ smiles: "O", reactant_metadata: { chemical_formula: "H2O" } }], + products: [{ smiles: "CCO", product_metadata: { chemical_formula: "C2H6O" } }], + }, + ], +}; + +describe("pathway utilities", () => { + test("builds a graph with a virtual root", () => { + const graph = buildPathwayGraph(sampleResult); + + expect(graph.virtualRoot.stepId).toBe("0"); + expect(graph.nodes).toHaveLength(3); + expect(graph.edges.map((edge) => edge.id)).toContain("edge-0-1"); + expect(graph.edges.map((edge) => edge.id)).toContain("edge-1-2"); + }); + + test("updates a step in place without mutating other steps", () => { + const next = updateStepInResult(sampleResult, "2", { + ...sampleResult.steps[1], + products: [{ smiles: "CO", product_metadata: { chemical_formula: "CH4O" } }], + }); + + expect(next.steps[1].products?.[0]?.smiles).toBe("CO"); + expect(sampleResult.steps[1].products?.[0]?.smiles).toBe("CCO"); + }); + + test("validates partial rerun eligibility from product count", () => { + expect(canPartialRerunStep(sampleResult.steps[0]).enabled).toBe(true); + expect( + canPartialRerunStep({ + ...sampleResult.steps[0], + products: [], + }).enabled, + ).toBe(false); + }); + + test("returns validation issues for malformed pathway payloads", () => { + const validation = validatePathwayResult({ steps: {} }); + + expect(validation.data).toBeUndefined(); + expect(validation.issues.length).toBeGreaterThan(0); + }); +}); diff --git a/viewer_new/src/lib/pathway.ts b/viewer_new/src/lib/pathway.ts new file mode 100644 index 00000000..c9eef714 --- /dev/null +++ b/viewer_new/src/lib/pathway.ts @@ -0,0 +1,293 @@ +import { ZodError } from "zod"; + +import { pathwayResultSchema, pathwayStepSchema } from "./schemas"; +import type { + MoleculeMetadata, + MoleculeRecord, + NormalizedMolecule, + NormalizedPathwayGraph, + NormalizedStepNode, + PathwayResult, + PathwayStep, + ValidationIssue, +} from "../types/viewer"; + +const VIRTUAL_ROOT_STEP = "0"; + +function getMetadata( + molecule: MoleculeRecord, + role: "product" | "reactant" | "reagent", +): MoleculeMetadata | undefined { + if (role === "product") { + return molecule.product_metadata; + } + if (role === "reactant") { + return molecule.reactant_metadata; + } + return molecule.reagent_metadata; +} + +function normalizeMolecules( + molecules: MoleculeRecord[] | undefined, + role: "product" | "reactant" | "reagent", + stepId: string, +): NormalizedMolecule[] { + return (molecules ?? []).map((molecule, index) => { + const metadata = getMetadata(molecule, role); + const label = metadata?.name || metadata?.chemical_formula || molecule.smiles; + + return { + key: `${stepId}-${role}-${index}`, + role, + smiles: molecule.smiles, + metadata, + label, + formula: metadata?.chemical_formula, + mass: metadata?.mass, + }; + }); +} + +function normalizeDependencyMap( + result: PathwayResult, +): Record { + const dependencyMap: Record = {}; + for (const [parent, children] of Object.entries(result.dependencies ?? {})) { + dependencyMap[String(parent)] = (children ?? []).map((child) => String(child)); + } + + if (result.steps.some((step) => String(step.step) === "1")) { + dependencyMap[VIRTUAL_ROOT_STEP] = ["1"]; + } else { + dependencyMap[VIRTUAL_ROOT_STEP] = []; + } + + return dependencyMap; +} + +function buildParentMap( + dependencyMap: Record, +): Record { + const parentMap: Record = {}; + + for (const [parent, children] of Object.entries(dependencyMap)) { + for (const child of children) { + if (!parentMap[child]) { + parentMap[child] = []; + } + parentMap[child].push(parent); + } + } + + return parentMap; +} + +function createVirtualRootNode(result: PathwayResult): NormalizedStepNode { + const primaryProduct = result.steps[0]?.products?.[0]; + const products = primaryProduct + ? normalizeMolecules([primaryProduct], "product", VIRTUAL_ROOT_STEP) + : []; + + return { + id: `step-${VIRTUAL_ROOT_STEP}`, + stepId: VIRTUAL_ROOT_STEP, + title: "Target Molecule", + isVirtualRoot: true, + products, + reactants: [], + reagents: [], + childIds: [], + parentIds: [], + conditions: result.steps[0]?.conditions, + metrics: result.steps[0]?.reactionmetrics?.[0], + }; +} + +export function buildPathwayGraph(result: PathwayResult): NormalizedPathwayGraph { + const parsed = pathwayResultSchema.parse(result); + const dependencyMap = normalizeDependencyMap(parsed); + const parentMap = buildParentMap(dependencyMap); + const stepMap = Object.fromEntries(parsed.steps.map((step) => [String(step.step), step])); + const virtualRoot = createVirtualRootNode(parsed); + + const nodes: NormalizedStepNode[] = [virtualRoot]; + const edges: NormalizedPathwayGraph["edges"] = []; + + const sortedSteps = [...parsed.steps].sort( + (left, right) => Number(left.step) - Number(right.step), + ); + + for (const step of sortedSteps) { + const stepId = String(step.step); + nodes.push({ + id: `step-${stepId}`, + stepId, + title: `Step ${stepId}`, + isVirtualRoot: false, + rawStep: step, + products: normalizeMolecules(step.products, "product", stepId), + reactants: normalizeMolecules(step.reactants, "reactant", stepId), + reagents: normalizeMolecules(step.reagents, "reagent", stepId), + metrics: step.reactionmetrics?.[0], + conditions: step.conditions, + childIds: dependencyMap[stepId] ?? [], + parentIds: parentMap[stepId] ?? [], + }); + } + + nodes[0].childIds = dependencyMap[VIRTUAL_ROOT_STEP] ?? []; + const validNodeIds = new Set(nodes.map((node) => node.id)); + + for (const [parent, children] of Object.entries(dependencyMap)) { + for (const child of children) { + if (parent === child) { + continue; + } + const source = `step-${parent}`; + const target = `step-${child}`; + if (!validNodeIds.has(source) || !validNodeIds.has(target)) { + continue; + } + edges.push({ + id: `edge-${parent}-${child}`, + source, + target, + }); + } + } + + return { + stepMap, + dependencyMap, + nodes, + edges, + virtualRoot, + }; +} + +export function validatePathwayResult(input: unknown) { + try { + return { + data: pathwayResultSchema.parse(input), + issues: [] as ValidationIssue[], + }; + } catch (error) { + if (error instanceof ZodError) { + return { + data: undefined, + issues: error.issues.map((issue) => ({ + path: issue.path.join("."), + message: issue.message, + })), + }; + } + return { + data: undefined, + issues: [{ path: "", message: "Unknown validation error" }], + }; + } +} + +export function validatePathwayStep(input: unknown) { + try { + return { + data: pathwayStepSchema.parse(input), + issues: [] as ValidationIssue[], + }; + } catch (error) { + if (error instanceof ZodError) { + return { + data: undefined, + issues: error.issues.map((issue) => ({ + path: issue.path.join("."), + message: issue.message, + })), + }; + } + return { + data: undefined, + issues: [{ path: "", message: "Unknown validation error" }], + }; + } +} + +export function getStep(result: PathwayResult | undefined, stepId: string) { + return result?.steps.find((step) => String(step.step) === stepId); +} + +export function updateStepInResult( + result: PathwayResult, + stepId: string, + nextStep: PathwayStep, +): PathwayResult { + return { + ...result, + steps: result.steps.map((step) => + String(step.step) === stepId ? nextStep : step, + ), + }; +} + +export function canPartialRerunStep(step: PathwayStep | undefined) { + if (!step) { + return { + enabled: false, + reason: "Select a valid step first.", + }; + } + + const products = step.products ?? []; + if (products.length !== 1) { + return { + enabled: false, + reason: "Partial rerun requires exactly one product SMILES on the selected step.", + }; + } + + if (!products[0]?.smiles) { + return { + enabled: false, + reason: "The selected step is missing a product SMILES string.", + }; + } + + return { + enabled: true, + reason: "", + }; +} + +export function prettyValidationIssues(issues: ValidationIssue[]) { + return issues.map((issue) => + issue.path ? `${issue.path}: ${issue.message}` : issue.message, + ); +} + +export function inferTopLevelSmiles(result: PathwayResult | undefined) { + return result?.steps[0]?.products?.[0]?.smiles ?? ""; +} + +export function formatConfidence(value: number | undefined) { + if (typeof value !== "number" || Number.isNaN(value)) { + return "n/a"; + } + return `${Math.round(value * 100)}%`; +} + +export function safeJsonParse(text: string) { + try { + return { + data: JSON.parse(text) as unknown, + error: "", + }; + } catch (error) { + return { + data: undefined, + error: error instanceof Error ? error.message : "Invalid JSON", + }; + } +} + +export function stringifyResult(result: PathwayResult | undefined) { + return JSON.stringify(result ?? { steps: [], dependencies: {} }, null, 2); +} diff --git a/viewer_new/src/lib/rdkit.ts b/viewer_new/src/lib/rdkit.ts new file mode 100644 index 00000000..6a449ab1 --- /dev/null +++ b/viewer_new/src/lib/rdkit.ts @@ -0,0 +1,51 @@ +import rdkitLoaderModule, { type RDKitLoader, type RDKitModule } from "@rdkit/rdkit"; +import rdkitWasm from "@rdkit/rdkit/dist/RDKit_minimal.wasm?url"; + +const svgCache = new Map(); + +let rdkitPromise: Promise | null = null; + +const initRDKitModule = ( + (rdkitLoaderModule as unknown as { default?: RDKitLoader }).default ?? + (rdkitLoaderModule as unknown as RDKitLoader) +) as RDKitLoader; + +export async function getRDKit() { + if (!rdkitPromise) { + rdkitPromise = initRDKitModule({ + locateFile: () => rdkitWasm, + }); + } + return rdkitPromise; +} + +export async function renderMoleculeSvg( + smiles: string, + width = 220, + height = 140, +) { + const cacheKey = `${smiles}:${width}:${height}`; + const cached = svgCache.get(cacheKey); + if (cached) { + return cached; + } + + const RDKit = await getRDKit(); + const molecule = RDKit.get_mol(smiles); + + if (!molecule || !molecule.is_valid()) { + molecule?.delete(); + throw new Error(`Unable to render SMILES: ${smiles}`); + } + + try { + if (!molecule.has_coords()) { + molecule.set_new_coords(true); + } + const svg = molecule.get_svg(width, height); + svgCache.set(cacheKey, svg); + return svg; + } finally { + molecule.delete(); + } +} diff --git a/viewer_new/src/lib/schemas.ts b/viewer_new/src/lib/schemas.ts new file mode 100644 index 00000000..18631718 --- /dev/null +++ b/viewer_new/src/lib/schemas.ts @@ -0,0 +1,96 @@ +import { z } from "zod"; + +export const moleculeMetadataSchema = z + .object({ + name: z.string().optional(), + chemical_formula: z.string().optional(), + mass: z.number().optional(), + smiles: z.string().optional(), + inchi: z.string().optional(), + }) + .catchall(z.unknown()); + +export const moleculeRecordSchema = z + .object({ + smiles: z.string(), + reactant_metadata: moleculeMetadataSchema.optional(), + reagent_metadata: moleculeMetadataSchema.optional(), + product_metadata: moleculeMetadataSchema.optional(), + }) + .catchall(z.unknown()); + +export const reactionMetricsSchema = z + .object({ + scalabilityindex: z.union([z.string(), z.number()]).optional(), + confidenceestimate: z.number().optional(), + closestliterature: z.string().optional(), + }) + .catchall(z.unknown()); + +export const pathwayStepSchema = z + .object({ + step: z.union([z.string(), z.number()]).transform((value) => String(value)), + reactants: z.array(moleculeRecordSchema).optional(), + reagents: z.array(moleculeRecordSchema).optional(), + products: z.array(moleculeRecordSchema).optional(), + conditions: z.union([z.record(z.string(), z.unknown()), z.array(z.unknown())]).optional(), + reactionmetrics: z.array(reactionMetricsSchema).optional(), + }) + .catchall(z.unknown()); + +export const pathwayResultSchema = z + .object({ + dependencies: z + .record(z.string(), z.array(z.union([z.string(), z.number()]))) + .optional(), + steps: z.array(pathwayStepSchema), + }) + .catchall(z.unknown()); + +export const runtimeConfigSchema = z.object({ + instances: z.array( + z.object({ + id: z.string(), + label: z.string(), + baseUrl: z.string(), + defaults: z.object({ + model_type: z.string(), + model_version: z.string(), + advanced_prompt: z.boolean(), + stability_flag: z.boolean(), + hallucination_check: z.boolean(), + use_protecting_group_feature: z.boolean().default(false), + }), + }), + ), + endpoints: z.object({ + retrosynthesis: z.string(), + rerun: z.string(), + partialRerun: z.string(), + saveEdited: z.string(), + health: z.string(), + }), +}); + +export const advancedSettingsSchema = z.object({ + llm_models: z.record( + z.string(), + z.object({ + internal_name: z.string(), + display_name: z.string(), + supports_advanced_prompt: z.boolean(), + supports_stability_check: z.boolean(), + supports_hallucination_check: z.boolean(), + supports_protecting_group_feature: z.boolean().optional(), + }), + ), + az_models: z.record(z.string(), z.object({ display_name: z.string() })), + defaults: z.object({ + model_type: z.string(), + model_version: z.string(), + advanced_prompt: z.boolean(), + stability_flag: z.boolean(), + hallucination_check: z.boolean(), + use_protecting_group_feature: z.boolean().default(false), + }), +}); diff --git a/viewer_new/src/main.tsx b/viewer_new/src/main.tsx new file mode 100644 index 00000000..5f5daad8 --- /dev/null +++ b/viewer_new/src/main.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { App } from "./app/App"; +import "./styles/global.css"; + +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + , +); diff --git a/viewer_new/src/styles/global.css b/viewer_new/src/styles/global.css new file mode 100644 index 00000000..26c0bbc7 --- /dev/null +++ b/viewer_new/src/styles/global.css @@ -0,0 +1,977 @@ +:root { + color-scheme: light; + --bg: #f3f7fb; + --panel: rgba(255, 255, 255, 0.88); + --panel-strong: #ffffff; + --panel-border: rgba(31, 45, 61, 0.1); + --text: #132033; + --text-muted: #5b6f86; + --brand: #13395b; + --brand-soft: #d8e6f5; + --accent: #ff8663; + --accent-soft: #ffe0d6; + --success: #1d8f63; + --warning: #c86a1b; + --error: #cf4b4b; + --shadow: 0 16px 48px rgba(19, 57, 91, 0.08); + --radius: 20px; + font-family: + "IBM Plex Sans", + "Space Grotesk", + "Avenir Next", + sans-serif; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-height: 100vh; + color: var(--text); + background: + radial-gradient(circle at top left, rgba(122, 210, 172, 0.25), transparent 28%), + radial-gradient(circle at top right, rgba(255, 134, 99, 0.18), transparent 22%), + linear-gradient(180deg, #f5f9fc 0%, #edf3f8 100%); +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + border: 0; +} + +#root { + min-height: 100vh; +} + +.app-shell { + min-height: 100vh; + padding: 24px; +} + +.boot-screen, +.busy-overlay { + position: fixed; + inset: 0; + display: grid; + place-items: center; +} + +.busy-overlay { + background: rgba(19, 32, 51, 0.24); + backdrop-filter: blur(10px); +} + +.boot-card, +.busy-card { + width: min(520px, calc(100vw - 32px)); +} + +.topbar { + display: grid; + gap: 20px; + padding: 24px; + border: 1px solid var(--panel-border); + border-radius: 32px; + background: linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(255, 255, 255, 0.78)); + box-shadow: var(--shadow); +} + +.brand, +.topbar__controls, +.topbar__status, +.panel__header, +.section-heading, +.instance-card__header, +.step-node__header, +.step-node__actions, +.inspector-actions, +.canvas-toolbar, +.badge-row { + display: flex; + align-items: center; + gap: 12px; +} + +.brand-mark { + display: grid; + place-items: center; + width: 44px; + height: 44px; + border-radius: 14px; + background: linear-gradient(135deg, var(--brand), #285f8f); + color: white; +} + +.brand h1, +.panel h2, +.panel h3, +.step-node h3, +.editor-drawer h2 { + margin: 0; +} + +.brand p, +.panel p, +.instance-card p, +.step-node p { + margin: 0; +} + +.eyebrow { + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.18em; + font-size: 0.7rem; + font-weight: 700; +} + +.topbar__controls { + flex-wrap: wrap; +} + +.topbar__status { + flex-wrap: wrap; +} + +.segmented-control, +.tab-row { + display: inline-flex; + padding: 4px; + border-radius: 999px; + background: rgba(19, 57, 91, 0.07); +} + +.segmented-control button, +.tab-row button { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border-radius: 999px; + background: transparent; + color: var(--text-muted); + cursor: pointer; +} + +.segmented-control button.active, +.tab-row button.active { + background: white; + color: var(--brand); + box-shadow: 0 6px 16px rgba(19, 57, 91, 0.1); +} + +.primary-button, +.ghost-button, +.instance-card__header, +.uploaded { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + border-radius: 14px; + padding: 11px 16px; + cursor: pointer; + transition: + transform 120ms ease, + background 120ms ease, + border-color 120ms ease; +} + +.primary-button { + color: white; + background: linear-gradient(135deg, var(--brand), #246194); +} + +.ghost-button, +.instance-card__header, +.uploaded { + color: var(--text); + background: rgba(255, 255, 255, 0.72); + border: 1px solid rgba(19, 57, 91, 0.1); +} + +.ghost-button:hover, +.primary-button:hover, +.instance-card__header:hover, +.uploaded:hover { + transform: translateY(-1px); +} + +.ghost-button:disabled, +.primary-button:disabled { + cursor: not-allowed; + opacity: 0.45; + transform: none; +} + +.ghost-button.danger { + color: var(--error); +} + +.upload-trigger { + white-space: nowrap; +} + +.api-key-field, +.smiles-field, +.search-field, +.form-grid label, +.editor-drawer label, +.editor-molecule-row label { + display: grid; + gap: 8px; +} + +.api-key-field, +.search-field { + position: relative; +} + +.api-key-field svg, +.search-field svg { + position: absolute; + top: 12px; + left: 12px; + color: var(--text-muted); +} + +.api-key-field input, +.search-field input { + padding-left: 38px; +} + +.smiles-field { + min-width: min(420px, 100%); + flex: 1; +} + +input, +select, +textarea { + width: 100%; + padding: 12px 14px; + border-radius: 14px; + border: 1px solid rgba(19, 57, 91, 0.14); + background: rgba(255, 255, 255, 0.8); + color: var(--text); +} + +textarea { + resize: vertical; +} + +.workspace { + display: grid; + grid-template-columns: 360px minmax(0, 1fr) 380px; + gap: 20px; + margin-top: 20px; +} + +.sidebar, +.main-column, +.inspector { + min-height: 0; +} + +.main-column { + display: grid; + gap: 20px; +} + +.panel { + border: 1px solid var(--panel-border); + border-radius: var(--radius); + background: var(--panel); + box-shadow: var(--shadow); + backdrop-filter: blur(10px); +} + +.sidebar-panel, +.inspector-panel, +.upload-panel, +.graph-panel { + padding: 18px; +} + +.stack { + display: grid; + gap: 16px; +} + +.instance-card { + display: grid; + gap: 14px; + padding: 16px; + border-radius: 20px; + border: 1px solid rgba(19, 57, 91, 0.1); + background: rgba(255, 255, 255, 0.56); +} + +.instance-card.active, +.uploaded.active { + border-color: rgba(255, 134, 99, 0.48); + box-shadow: 0 0 0 3px rgba(255, 134, 99, 0.12); +} + +.instance-card__header { + justify-content: space-between; + padding: 0; + text-align: left; +} + +.instance-card__meta, +.toggle-group { + display: grid; + gap: 8px; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.form-grid .full-span { + grid-column: 1 / -1; +} + +.toggle-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + color: var(--text); + padding: 10px 12px; + border-radius: 16px; + background: rgba(19, 57, 91, 0.04); + border: 1px solid rgba(19, 57, 91, 0.08); + cursor: pointer; +} + +.toggle-row.disabled { + color: var(--text-muted); + opacity: 0.6; + cursor: not-allowed; +} + +.toggle-row__label { + font-weight: 600; +} + +.toggle-switch__input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.toggle-switch { + position: relative; + width: 48px; + height: 28px; + border-radius: 999px; + background: rgba(19, 57, 91, 0.16); + transition: background 140ms ease; +} + +.toggle-switch__thumb { + position: absolute; + top: 3px; + left: 3px; + width: 22px; + height: 22px; + border-radius: 50%; + background: white; + box-shadow: 0 8px 16px rgba(19, 57, 91, 0.18); + transition: transform 140ms ease; +} + +.toggle-switch__input:checked + .toggle-switch { + background: linear-gradient(135deg, var(--brand), #2d79b8); +} + +.toggle-switch__input:checked + .toggle-switch .toggle-switch__thumb { + transform: translateX(20px); +} + +.toggle-switch__input:focus-visible + .toggle-switch { + outline: 2px solid rgba(36, 97, 148, 0.35); + outline-offset: 2px; +} + +.status-pill, +.health-pill, +.badge, +.molecule-chip { + display: inline-flex; + align-items: center; + gap: 6px; + width: fit-content; + padding: 6px 10px; + border-radius: 999px; + font-size: 0.82rem; + font-weight: 600; +} + +.status-pill { + background: rgba(19, 57, 91, 0.08); +} + +.status-pill.success, +.health-pill.healthy { + color: var(--success); + background: rgba(29, 143, 99, 0.12); +} + +.status-pill.error, +.health-pill.error, +.health-pill.unauthorized { + color: var(--error); + background: rgba(207, 75, 75, 0.12); +} + +.status-pill.dirty { + color: var(--warning); + background: rgba(200, 106, 27, 0.12); +} + +.status-pill.pending { + color: var(--brand); +} + +.badge { + color: var(--brand); + background: var(--brand-soft); +} + +.badge.subtle, +.molecule-chip { + color: var(--text-muted); + background: rgba(19, 57, 91, 0.08); +} + +.molecule-chip.more { + color: var(--accent); + background: var(--accent-soft); +} + +.graph-panel { + min-height: 68vh; + display: grid; + gap: 14px; +} + +.canvas-shell { + position: relative; + min-height: 60vh; + border-radius: 20px; + overflow: hidden; + background: + linear-gradient(180deg, rgba(14, 31, 47, 0.98), rgba(20, 40, 61, 0.98)); +} + +.canvas-flow { + height: min(72vh, 880px); +} + +.canvas-toolbar { + justify-content: space-between; + padding: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(9, 19, 29, 0.44); +} + +.canvas-toolbar .ghost-button { + color: white; + border-color: rgba(255, 255, 255, 0.15); + background: rgba(255, 255, 255, 0.08); +} + +.canvas-empty { + min-height: 60vh; + display: grid; + place-items: center; + text-align: center; + color: white; + border-radius: 20px; + background: linear-gradient(180deg, rgba(14, 31, 47, 0.98), rgba(20, 40, 61, 0.98)); +} + +.canvas-empty.error { + color: #ffd8d8; +} + +.step-node { + position: relative; + display: grid; + place-items: center; + padding: 14px; + border-radius: 28px; + border: 1px solid rgba(255, 255, 255, 0.14); + background: + radial-gradient(circle at top, rgba(255, 255, 255, 0.92), rgba(244, 249, 255, 0.96)), + linear-gradient(180deg, rgba(250, 252, 255, 0.98), rgba(245, 248, 252, 0.96)); + box-shadow: 0 12px 24px rgba(10, 16, 24, 0.12); + overflow: hidden; +} + +.step-node__handle { + width: 1px; + height: 1px; + opacity: 0; + border: 0; + background: transparent; +} + +.step-node.root { + background: linear-gradient(180deg, rgba(221, 250, 236, 0.98), rgba(241, 255, 248, 0.98)); +} + +.step-node.selected { + border-color: rgba(255, 134, 99, 0.58); + box-shadow: 0 12px 24px rgba(255, 134, 99, 0.12); +} + +.step-node.muted { + opacity: 0.4; +} + +.step-node__product { + display: grid; + width: 100%; + place-items: center; +} + +.step-node__product.solo .molecule-svg, +.step-node__product.solo .molecule-loading, +.step-node__product.solo .molecule-fallback { + width: 100%; + min-height: 220px; +} + +.step-node__placeholder, +.molecule-loading, +.molecule-fallback { + display: grid; + place-items: center; + min-height: 110px; + padding: 12px; + text-align: center; + border-radius: 16px; + background: rgba(19, 57, 91, 0.06); + color: var(--text-muted); +} + +.molecule-loading.compact, +.molecule-fallback.compact { + min-height: 90px; +} + +.molecule-svg { + display: grid; + place-items: center; + overflow: hidden; + border-radius: 16px; + background: white; + padding: 8px; +} + +.molecule-svg.compact { + min-height: 88px; +} + +.molecule-svg svg { + display: block; + width: auto; + height: auto; + max-width: 100%; + max-height: 100%; + shape-rendering: geometricPrecision; + text-rendering: geometricPrecision; +} + +.molecule-chip-group { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.step-node__overlay { + position: absolute; + inset: 0; + display: grid; + grid-template-rows: auto auto 1fr; + align-content: start; + gap: 16px; + padding: 18px; + background: + radial-gradient(circle at top right, rgba(122, 210, 172, 0.22), transparent 34%), + radial-gradient(circle at bottom left, rgba(255, 134, 99, 0.18), transparent 32%), + linear-gradient(180deg, rgba(18, 47, 79, 0.86), rgba(12, 33, 56, 0.9)); + color: #f8fbff; + opacity: 0; + transform: translateY(8px); + transition: + opacity 140ms ease, + transform 140ms ease; +} + +.step-node:hover .step-node__overlay, +.step-node:focus-visible .step-node__overlay, +.step-node:focus-within .step-node__overlay { + opacity: 1; + transform: translateY(0); +} + +.step-node__overlay-header { + display: grid; + gap: 10px; +} + +.step-node__overlay .badge-row { + flex-wrap: wrap; + gap: 8px; +} + +.step-node__overlay .eyebrow, +.step-node__overlay .badge, +.step-node__overlay .badge.subtle, +.step-node__overlay .molecule-chip { + color: white; +} + +.step-node__overlay .badge { + background: rgba(255, 255, 255, 0.16); +} + +.step-node__overlay .badge.subtle, +.step-node__overlay .molecule-chip { + background: rgba(255, 255, 255, 0.1); +} + +.step-node__overlay-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +.step-node__actions { + align-self: end; + justify-content: flex-start; + flex-wrap: wrap; + gap: 12px; + margin-top: 8px; +} + +.step-node__actions .ghost-button { + min-height: 40px; + padding: 10px 14px; + color: #f8fbff; + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.24); + box-shadow: none; +} + +.step-node__actions .ghost-button:hover { + background: rgba(255, 255, 255, 0.18); +} + +.react-flow__edges { + z-index: 2; +} + +.react-flow__edge-path { + stroke: white; + stroke-width: 4; + stroke-opacity: 1; +} + +.react-flow__arrowhead path, +.react-flow__marker path { + fill: white; + stroke: white; +} + +.inspector { + display: grid; +} + +.inspector-panel { + height: calc(100vh - 180px); + overflow: auto; +} + +.inspector-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.metric-card { + display: grid; + gap: 8px; + padding: 14px; + border-radius: 16px; + background: rgba(19, 57, 91, 0.06); +} + +.metric-card span { + color: var(--text-muted); + font-size: 0.82rem; +} + +.inspector-section, +.editor-section { + display: grid; + gap: 12px; + margin-top: 18px; +} + +.inspector-molecules, +.editor-molecule-list, +.editor-condition-list { + display: grid; + gap: 12px; +} + +.inspector-molecule, +.editor-molecule-row, +.editor-condition-row { + display: grid; + gap: 12px; + padding: 14px; + border-radius: 16px; + background: rgba(255, 255, 255, 0.72); + border: 1px solid rgba(19, 57, 91, 0.08); +} + +.editor-condition-row { + grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; + align-items: end; +} + +.empty-note { + padding: 14px; + border-radius: 16px; + background: rgba(19, 57, 91, 0.04); + color: var(--text-muted); +} + +.inspector-molecule p { + overflow-wrap: anywhere; +} + +.conditions-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.condition-card { + display: grid; + gap: 8px; + padding: 14px; + border-radius: 16px; + background: rgba(19, 57, 91, 0.06); +} + +.condition-card span { + color: var(--text-muted); + font-size: 0.82rem; + text-transform: capitalize; +} + +.condition-card strong { + overflow-wrap: anywhere; +} + +.action-chip { + cursor: pointer; + border: 1px solid transparent; +} + +.action-chip.active { + color: var(--accent); + border-color: rgba(255, 134, 99, 0.4); + background: rgba(255, 224, 214, 0.92); +} + +.json-details { + margin-top: 18px; + border: 1px solid rgba(19, 57, 91, 0.08); + border-radius: 16px; + background: rgba(255, 255, 255, 0.48); +} + +.json-details summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 14px 16px; + cursor: pointer; + list-style: none; + font-weight: 600; +} + +.json-details summary::-webkit-details-marker { + display: none; +} + +.json-details[open] summary svg { + transform: rotate(180deg); +} + +.json-details pre { + margin: 0 14px 14px; +} + +.json-block { + overflow: auto; + padding: 12px; + border-radius: 16px; + background: #0e1f2f; + color: #dbe6f2; + font-family: + "SFMono-Regular", + "IBM Plex Mono", + monospace; + font-size: 0.84rem; + line-height: 1.5; +} + +.json-block.tall { + max-height: 260px; +} + +.upload-dropzone { + display: grid; + place-items: center; + gap: 10px; + min-height: 180px; + margin-top: 16px; + border: 1px dashed rgba(19, 57, 91, 0.2); + border-radius: 20px; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.6), rgba(241, 247, 253, 0.72)); + text-align: center; + cursor: pointer; +} + +.editor-drawer__backdrop { + position: fixed; + inset: 0; + z-index: 40; + display: flex; + justify-content: flex-end; + background: rgba(9, 19, 29, 0.32); + backdrop-filter: blur(10px); +} + +.editor-drawer { + width: min(760px, 100vw); + height: 100vh; + padding: 20px; + overflow: auto; + background: #f7fbff; + box-shadow: -20px 0 40px rgba(9, 19, 29, 0.2); +} + +.editor-drawer__content { + display: grid; + gap: 16px; + margin-top: 16px; +} + +.editor-drawer__footer { + display: flex; + justify-content: flex-end; +} + +.inline-error, +.inline-warning { + padding: 12px 14px; + border-radius: 14px; + font-size: 0.92rem; +} + +.inline-error { + color: var(--error); + background: rgba(207, 75, 75, 0.12); +} + +.inline-warning { + color: var(--warning); + background: rgba(200, 106, 27, 0.12); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.spin { + animation: spin 0.9s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@media (max-width: 1360px) { + .workspace { + grid-template-columns: 320px minmax(0, 1fr); + } + + .inspector { + grid-column: 1 / -1; + } + + .inspector-panel { + height: auto; + } +} + +@media (max-width: 960px) { + .app-shell { + padding: 16px; + } + + .workspace { + grid-template-columns: 1fr; + } + + .topbar { + padding: 18px; + } + + .graph-panel { + min-height: 56vh; + } + + .step-node__product { + grid-template-columns: 1fr; + } + + .form-grid, + .inspector-grid, + .conditions-grid { + grid-template-columns: 1fr; + } +} diff --git a/viewer_new/src/test/setup.ts b/viewer_new/src/test/setup.ts new file mode 100644 index 00000000..f149f27a --- /dev/null +++ b/viewer_new/src/test/setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom/vitest"; diff --git a/viewer_new/src/types/viewer.ts b/viewer_new/src/types/viewer.ts new file mode 100644 index 00000000..f95eeaa9 --- /dev/null +++ b/viewer_new/src/types/viewer.ts @@ -0,0 +1,173 @@ +export type ToggleableModelFlags = { + advanced_prompt: boolean; + stability_flag: boolean; + hallucination_check: boolean; + use_protecting_group_feature: boolean; +}; + +export type InstanceDefaults = ToggleableModelFlags & { + model_type: string; + model_version: string; +}; + +export type ViewerRuntimeConfig = { + instances: Array<{ + id: string; + label: string; + baseUrl: string; + defaults: InstanceDefaults; + }>; + endpoints: { + retrosynthesis: string; + rerun: string; + partialRerun: string; + saveEdited: string; + health: string; + }; +}; + +export type AdvancedSettingsConfig = { + llm_models: Record< + string, + { + internal_name: string; + display_name: string; + supports_advanced_prompt: boolean; + supports_stability_check: boolean; + supports_hallucination_check: boolean; + supports_protecting_group_feature?: boolean; + } + >; + az_models: Record; + defaults: InstanceDefaults; +}; + +export type MoleculeMetadata = { + name?: string; + chemical_formula?: string; + mass?: number; + smiles?: string; + inchi?: string; + [key: string]: unknown; +}; + +export type MoleculeRecord = { + smiles: string; + reactant_metadata?: MoleculeMetadata; + reagent_metadata?: MoleculeMetadata; + product_metadata?: MoleculeMetadata; + [key: string]: unknown; +}; + +export type ReactionMetrics = { + scalabilityindex?: string | number; + confidenceestimate?: number; + closestliterature?: string; + [key: string]: unknown; +}; + +export type PathwayStep = { + step: string; + reactants?: MoleculeRecord[]; + reagents?: MoleculeRecord[]; + products?: MoleculeRecord[]; + conditions?: Record | unknown[]; + reactionmetrics?: ReactionMetrics[]; + [key: string]: unknown; +}; + +export type PathwayResult = { + dependencies?: Record>; + steps: PathwayStep[]; + [key: string]: unknown; +}; + +export type InstanceRequestSettings = InstanceDefaults; + +export type AnalysisRequest = InstanceRequestSettings & { + smiles: string; +}; + +export type ViewerMode = "analyze" | "upload"; +export type ViewerRunSource = "api" | "file"; +export type ViewerRunStatus = "idle" | "pending" | "success" | "error"; +export type HealthState = "unknown" | "healthy" | "unauthorized" | "error"; + +export type ValidationIssue = { + path: string; + message: string; +}; + +export type NormalizedMolecule = { + key: string; + role: "product" | "reactant" | "reagent"; + smiles: string; + metadata?: MoleculeMetadata; + label: string; + formula?: string; + mass?: number; +}; + +export type NormalizedStepNode = { + id: string; + stepId: string; + title: string; + isVirtualRoot: boolean; + rawStep?: PathwayStep; + products: NormalizedMolecule[]; + reactants: NormalizedMolecule[]; + reagents: NormalizedMolecule[]; + metrics?: ReactionMetrics; + conditions?: Record | unknown[]; + childIds: string[]; + parentIds: string[]; +}; + +export type NormalizedPathwayGraph = { + stepMap: Record; + dependencyMap: Record; + nodes: NormalizedStepNode[]; + edges: Array<{ + id: string; + source: string; + target: string; + }>; + virtualRoot: NormalizedStepNode; +}; + +export type ViewerRun = { + key: string; + label: string; + source: ViewerRunSource; + status: ViewerRunStatus; + instanceId?: string; + request?: AnalysisRequest; + rawResult?: PathwayResult; + graph?: NormalizedPathwayGraph; + error?: string; + durationMs?: number; + dirty: boolean; + lastUpdatedAt: number; +}; + +export type HealthStatusMap = Record< + string, + { + state: HealthState; + message: string; + } +>; + +export type ViewerStoreState = { + runtimeConfig?: ViewerRuntimeConfig; + advancedSettings?: AdvancedSettingsConfig; + mode: ViewerMode; + apiKey: string; + currentSmiles: string; + activeRunKey?: string; + selectedStepId?: string; + runs: Record; + instanceSettings: Record; + health: HealthStatusMap; + editorOpen: boolean; +}; diff --git a/viewer_new/tsconfig.app.json b/viewer_new/tsconfig.app.json new file mode 100644 index 00000000..a9b5a59c --- /dev/null +++ b/viewer_new/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/viewer_new/tsconfig.json b/viewer_new/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/viewer_new/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/viewer_new/tsconfig.node.json b/viewer_new/tsconfig.node.json new file mode 100644 index 00000000..8a67f62f --- /dev/null +++ b/viewer_new/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/viewer_new/vite.config.ts b/viewer_new/vite.config.ts new file mode 100644 index 00000000..bbfb6a83 --- /dev/null +++ b/viewer_new/vite.config.ts @@ -0,0 +1,12 @@ +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + setupFiles: "./src/test/setup.ts", + css: true, + }, +});