From 609184ac22ac26b5b0281a06bbc43c6459c2daed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Wed, 1 Jul 2026 12:04:39 +0200 Subject: [PATCH 01/81] feat: refreshing UX/UI --- package-lock.json | 18902 ++++++++++++++++ webui/index.html | 3 - webui/package.json | 3 + webui/src/components/categories.ts | 55 + webui/src/components/category-card.tsx | 57 + webui/src/components/category-list-item.tsx | 55 + webui/src/components/category-pill.tsx | 49 + webui/src/components/extension-card.tsx | 197 + .../extension-list.tsx | 50 +- .../src/components/extension-searchfield.tsx | 144 + webui/src/components/kbd-key.tsx | 32 + webui/src/components/openvsx-mark.tsx | 14 + webui/src/components/sanitized-markdown.tsx | 2 + webui/src/components/shortcuts-modal.tsx | 78 + .../use-grid-keyboard-navigation.ts | 141 + webui/src/default/default-app.tsx | 7 + webui/src/default/menu-content.tsx | 47 +- webui/src/default/page-settings.tsx | 49 +- webui/src/default/theme.tsx | 220 +- webui/src/keyboard-shortcuts-context.tsx | 138 + webui/src/layout/app-footer.tsx | 241 + webui/src/layout/app-layout.tsx | 135 + webui/src/layout/app-navbar.tsx | 156 + webui/src/layout/nav-search-field.tsx | 102 + webui/src/main.css | 60 +- webui/src/main.tsx | 4 +- webui/src/nav-search-context.tsx | 44 + webui/src/other-pages.tsx | 157 - webui/src/pages/browse/browse-header.tsx | 124 + webui/src/pages/browse/browse-page.tsx | 142 + webui/src/pages/browse/use-browse-filter.ts | 139 + .../extension-detail-overview.tsx | 4 +- .../extension-detail/extension-detail.tsx | 99 +- .../extension-list-container.tsx | 109 - .../extension-list/extension-list-header.tsx | 216 - .../extension-list/extension-list-item.tsx | 120 - .../extension-list/extension-list-routes.ts | 1 + .../extension-list-searchfield.tsx | 89 - webui/src/pages/home/home-page.tsx | 59 + webui/src/pages/home/homepage-view.tsx | 592 + .../namespace-detail/namespace-detail.tsx | 4 +- webui/src/pages/user/avatar.tsx | 162 +- webui/src/use-shortcut.ts | 35 + 43 files changed, 22200 insertions(+), 837 deletions(-) create mode 100644 package-lock.json create mode 100644 webui/src/components/categories.ts create mode 100644 webui/src/components/category-card.tsx create mode 100644 webui/src/components/category-list-item.tsx create mode 100644 webui/src/components/category-pill.tsx create mode 100644 webui/src/components/extension-card.tsx rename webui/src/{pages/extension-list => components}/extension-list.tsx (82%) create mode 100644 webui/src/components/extension-searchfield.tsx create mode 100644 webui/src/components/kbd-key.tsx create mode 100644 webui/src/components/openvsx-mark.tsx create mode 100644 webui/src/components/shortcuts-modal.tsx create mode 100644 webui/src/components/use-grid-keyboard-navigation.ts create mode 100644 webui/src/keyboard-shortcuts-context.tsx create mode 100644 webui/src/layout/app-footer.tsx create mode 100644 webui/src/layout/app-layout.tsx create mode 100644 webui/src/layout/app-navbar.tsx create mode 100644 webui/src/layout/nav-search-field.tsx create mode 100644 webui/src/nav-search-context.tsx delete mode 100644 webui/src/other-pages.tsx create mode 100644 webui/src/pages/browse/browse-header.tsx create mode 100644 webui/src/pages/browse/browse-page.tsx create mode 100644 webui/src/pages/browse/use-browse-filter.ts delete mode 100644 webui/src/pages/extension-list/extension-list-container.tsx delete mode 100644 webui/src/pages/extension-list/extension-list-header.tsx delete mode 100644 webui/src/pages/extension-list/extension-list-item.tsx delete mode 100644 webui/src/pages/extension-list/extension-list-searchfield.tsx create mode 100644 webui/src/pages/home/home-page.tsx create mode 100644 webui/src/pages/home/homepage-view.tsx create mode 100644 webui/src/use-shortcut.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..905b6fa83 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,18902 @@ +{ + "name": "openvsx", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "workspaces": [ + "cli", + "webui" + ] + }, + "cli": { + "name": "ovsx", + "version": "1.0.2", + "license": "EPL-2.0", + "dependencies": { + "@vscode/vsce": "^3.7.1", + "commander": "^6.2.1", + "follow-redirects": "^1.16.0", + "is-ci": "^2.0.0", + "leven": "^3.1.0", + "semver": "^7.6.0", + "tmp": "^0.2.3", + "yauzl-promise": "^4.0.0" + }, + "bin": { + "ovsx": "bin/ovsx" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.15.0", + "@stylistic/eslint-plugin": "^2.11.0", + "@types/follow-redirects": "^1.13.1", + "@types/is-ci": "^2.0.0", + "@types/node": "^20.14.8", + "@types/semver": "^7.5.8", + "@types/tmp": "^0.2.2", + "@types/yauzl-promise": "^4", + "@typescript-eslint/eslint-plugin": "^8.15.0", + "@typescript-eslint/parser": "^8.15.0", + "eslint": "^9.15.0", + "limiter": "^2.1.0", + "rimraf": "^6.0.1", + "typescript": "^5.6.3" + }, + "engines": { + "node": ">= 20" + } + }, + "cli/node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/@azu/format-text": { + "version": "1.0.2", + "license": "BSD-3-Clause" + }, + "cli/node_modules/@azu/style-format": { + "version": "1.0.1", + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, + "cli/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/core-auth": { + "version": "1.8.0", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/core-client": { + "version": "1.9.2", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.6.1", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/core-rest-pipeline": { + "version": "1.17.0", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-auth": "^1.8.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.9.0", + "@azure/logger": "^1.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/core-tracing": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/core-util": { + "version": "1.10.0", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/identity": { + "version": "4.4.1", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.5.0", + "@azure/core-client": "^1.9.2", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^3.14.0", + "@azure/msal-node": "^2.9.2", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/identity/node_modules/@azure/abort-controller": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "cli/node_modules/@azure/identity/node_modules/jwa": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "cli/node_modules/@azure/identity/node_modules/jws": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cli/node_modules/@azure/logger": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@azure/msal-browser": { + "version": "3.24.0", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.15.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "cli/node_modules/@azure/msal-common": { + "version": "14.15.0", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "cli/node_modules/@azure/msal-node": { + "version": "2.14.0", + "license": "MIT", + "dependencies": { + "@azure/msal-common": "14.15.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "cli/node_modules/@babel/code-frame": { + "version": "7.27.1", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "cli/node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "cli/node_modules/@babel/runtime": { + "version": "7.27.0", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "cli/node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "cli/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "cli/node_modules/@eslint/config-array": { + "version": "0.19.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "cli/node_modules/@eslint/core": { + "version": "0.9.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "cli/node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "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.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/@eslint/js": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "cli/node_modules/@eslint/object-schema": { + "version": "2.1.4", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "cli/node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "cli/node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "cli/node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "cli/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "cli/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "cli/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "cli/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "cli/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "cli/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "cli/node_modules/@node-rs/crc32": { + "version": "1.10.6", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@node-rs/crc32-android-arm-eabi": "1.10.6", + "@node-rs/crc32-android-arm64": "1.10.6", + "@node-rs/crc32-darwin-arm64": "1.10.6", + "@node-rs/crc32-darwin-x64": "1.10.6", + "@node-rs/crc32-freebsd-x64": "1.10.6", + "@node-rs/crc32-linux-arm-gnueabihf": "1.10.6", + "@node-rs/crc32-linux-arm64-gnu": "1.10.6", + "@node-rs/crc32-linux-arm64-musl": "1.10.6", + "@node-rs/crc32-linux-x64-gnu": "1.10.6", + "@node-rs/crc32-linux-x64-musl": "1.10.6", + "@node-rs/crc32-wasm32-wasi": "1.10.6", + "@node-rs/crc32-win32-arm64-msvc": "1.10.6", + "@node-rs/crc32-win32-ia32-msvc": "1.10.6", + "@node-rs/crc32-win32-x64-msvc": "1.10.6" + } + }, + "cli/node_modules/@node-rs/crc32-darwin-arm64": { + "version": "1.10.6", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "cli/node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/@npmcli/agent": { + "version": "4.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.4", + "extraneous": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "11.2.5", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "cli/node_modules/@npmcli/fs": { + "version": "5.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/config-loader/node_modules/ajv": { + "version": "8.18.0", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "cli/node_modules/@secretlint/config-loader/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/@secretlint/config-loader/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "cli/node_modules/@secretlint/core": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/core/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/@secretlint/formatter": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "cli/node_modules/@secretlint/formatter/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/@secretlint/formatter/node_modules/pluralize": { + "version": "8.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "cli/node_modules/@secretlint/formatter/node_modules/strip-ansi": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "cli/node_modules/@secretlint/node": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/node/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/@secretlint/profiler": { + "version": "10.2.2", + "license": "MIT" + }, + "cli/node_modules/@secretlint/resolver": { + "version": "10.2.2", + "license": "MIT" + }, + "cli/node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "cli/node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@secretlint/types": { + "version": "10.2.2", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/@stylistic/eslint-plugin": { + "version": "2.11.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.13.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "cli/node_modules/@textlint/ast-node-types": { + "version": "15.5.0", + "license": "MIT" + }, + "cli/node_modules/@textlint/linter-formatter": { + "version": "15.5.0", + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.5.0", + "@textlint/resolver": "15.5.0", + "@textlint/types": "15.5.0", + "chalk": "^4.1.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "lodash": "^4.17.21", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "cli/node_modules/@textlint/linter-formatter/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/@textlint/module-interop": { + "version": "15.5.0", + "license": "MIT" + }, + "cli/node_modules/@textlint/resolver": { + "version": "15.5.0", + "license": "MIT" + }, + "cli/node_modules/@textlint/types": { + "version": "15.5.0", + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.5.0" + } + }, + "cli/node_modules/@types/ci-info": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/follow-redirects": { + "version": "1.14.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "cli/node_modules/@types/follow-redirects/node_modules/@types/node": { + "version": "20.11.30", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "cli/node_modules/@types/is-ci": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ci-info": "*" + } + }, + "cli/node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/node": { + "version": "20.16.7", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "cli/node_modules/@types/node/node_modules/undici-types": { + "version": "6.19.8", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "license": "MIT" + }, + "cli/node_modules/@types/sarif": { + "version": "2.1.7", + "license": "MIT" + }, + "cli/node_modules/@types/semver": { + "version": "7.5.8", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/tmp": { + "version": "0.2.6", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/@types/yauzl-promise": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "cli/node_modules/@types/yauzl-promise/node_modules/@types/node": { + "version": "20.11.30", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "cli/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/type-utils": "8.15.0", + "@typescript-eslint/utils": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "cli/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "cli/node_modules/@typescript-eslint/parser": { + "version": "8.15.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "cli/node_modules/@typescript-eslint/scope-manager": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "cli/node_modules/@typescript-eslint/type-utils": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.15.0", + "@typescript-eslint/utils": "8.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "cli/node_modules/@typescript-eslint/types": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "cli/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.15.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/visitor-keys": "8.15.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "cli/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "cli/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.9", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/@typescript-eslint/utils": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.15.0", + "@typescript-eslint/types": "8.15.0", + "@typescript-eslint/typescript-estree": "8.15.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "cli/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.15.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "cli/node_modules/@vscode/vsce": { + "version": "3.7.1", + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "cli/node_modules/@vscode/vsce-sign": { + "version": "2.0.4", + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.2", + "@vscode/vsce-sign-alpine-x64": "2.0.2", + "@vscode/vsce-sign-darwin-arm64": "2.0.2", + "@vscode/vsce-sign-darwin-x64": "2.0.2", + "@vscode/vsce-sign-linux-arm": "2.0.2", + "@vscode/vsce-sign-linux-arm64": "2.0.2", + "@vscode/vsce-sign-linux-x64": "2.0.2", + "@vscode/vsce-sign-win32-arm64": "2.0.2", + "@vscode/vsce-sign-win32-x64": "2.0.2" + } + }, + "cli/node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.2", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "cli/node_modules/@vscode/vsce/node_modules/balanced-match": { + "version": "4.0.4", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "cli/node_modules/@vscode/vsce/node_modules/brace-expansion": { + "version": "5.0.5", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "cli/node_modules/@vscode/vsce/node_modules/commander": { + "version": "12.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "cli/node_modules/@vscode/vsce/node_modules/glob": { + "version": "11.1.0", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/@vscode/vsce/node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/abbrev": { + "version": "4.0.0", + "extraneous": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/acorn": { + "version": "8.14.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "cli/node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "cli/node_modules/agent-base": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/ajv": { + "version": "6.14.0", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "cli/node_modules/ansi-escapes": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/ansi-regex": { + "version": "6.0.1", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "cli/node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "cli/node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "cli/node_modules/astral-regex": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/async-function": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/async-generator-function": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "cli/node_modules/azure-devops-node-api": { + "version": "12.5.0", + "license": "MIT", + "dependencies": { + "tunnel": "0.0.6", + "typed-rest-client": "^1.8.4" + } + }, + "cli/node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "cli/node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "cli/node_modules/binaryextensions": { + "version": "6.11.0", + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "cli/node_modules/bl": { + "version": "4.1.0", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "cli/node_modules/boolbase": { + "version": "1.0.0", + "license": "ISC" + }, + "cli/node_modules/boundary": { + "version": "2.0.0", + "license": "BSD-2-Clause" + }, + "cli/node_modules/brace-expansion": { + "version": "1.1.13", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "cli/node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "cli/node_modules/buffer-crc32": { + "version": "0.2.13", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "cli/node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "license": "BSD-3-Clause" + }, + "cli/node_modules/cacache": { + "version": "20.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/cacache/node_modules/lru-cache": { + "version": "11.2.5", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "cli/node_modules/cacache/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/call-bound": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "cli/node_modules/cheerio": { + "version": "1.0.0-rc.12", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "cli/node_modules/cheerio-select": { + "version": "2.1.0", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "cli/node_modules/chownr": { + "version": "3.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "cli/node_modules/ci-info": { + "version": "2.0.0", + "license": "MIT" + }, + "cli/node_modules/cockatiel": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "cli/node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "cli/node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "cli/node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "cli/node_modules/commander": { + "version": "6.2.1", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "cli/node_modules/concat-map": { + "version": "0.0.1", + "license": "MIT" + }, + "cli/node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/css-select": { + "version": "5.1.0", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "cli/node_modules/css-what": { + "version": "6.1.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "cli/node_modules/debug": { + "version": "4.3.4", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "cli/node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/deep-extend": { + "version": "0.6.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "cli/node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/define-data-property": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/define-data-property/node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/define-data-property/node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/define-data-property/node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/define-data-property/node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/define-lazy-prop": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/define-properties": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "cli/node_modules/detect-libc": { + "version": "2.0.3", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/dom-serializer": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "cli/node_modules/domelementtype": { + "version": "2.3.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "cli/node_modules/domhandler": { + "version": "5.0.3", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "cli/node_modules/domutils": { + "version": "3.1.0", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "cli/node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/eastasianwidth": { + "version": "0.2.0", + "license": "MIT" + }, + "cli/node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "cli/node_modules/editions": { + "version": "6.22.0", + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "cli/node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "cli/node_modules/encoding": { + "version": "0.1.13", + "extraneous": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "cli/node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "cli/node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "cli/node_modules/env-paths": { + "version": "2.2.1", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/environment": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/err-code": { + "version": "2.0.3", + "extraneous": true, + "license": "MIT" + }, + "cli/node_modules/es-define-property": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/es-errors": { + "version": "1.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/es-object-atoms": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/es-set-tostringtag": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/es-set-tostringtag/node_modules/get-intrinsic": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/eslint": { + "version": "9.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.15.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.5", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.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.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "cli/node_modules/eslint-scope": { + "version": "8.2.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/eslint/node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "cli/node_modules/eslint/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "cli/node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "cli/node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "cli/node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "cli/node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "cli/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "cli/node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/events": { + "version": "3.3.0", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "cli/node_modules/expand-template": { + "version": "2.0.3", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/exponential-backoff": { + "version": "3.1.1", + "extraneous": true, + "license": "Apache-2.0" + }, + "cli/node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "cli/node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "cli/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/fast-uri": { + "version": "3.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "cli/node_modules/fastq": { + "version": "1.17.1", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "cli/node_modules/fd-slicer": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "cli/node_modules/fdir": { + "version": "6.5.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "cli/node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "cli/node_modules/fill-range": { + "version": "7.1.1", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "cli/node_modules/flatted": { + "version": "3.4.2", + "dev": true, + "license": "ISC" + }, + "cli/node_modules/follow-redirects": { + "version": "1.16.0", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "cli/node_modules/foreground-child": { + "version": "3.3.1", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/form-data": { + "version": "4.0.4", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "cli/node_modules/fs-constants": { + "version": "1.0.0", + "license": "MIT", + "optional": true + }, + "cli/node_modules/fs-extra": { + "version": "11.3.2", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "cli/node_modules/fs-minipass": { + "version": "3.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "cli/node_modules/fs-minipass/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/generator-function": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/get-intrinsic": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/get-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/github-from-package": { + "version": "0.0.0", + "license": "MIT", + "optional": true + }, + "cli/node_modules/glob": { + "version": "13.0.0", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "cli/node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "cli/node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "cli/node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/globalthis": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/globalthis/node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/globalthis/node_modules/gopd": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/globalthis/node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/globby": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/globby/node_modules/fast-glob": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "cli/node_modules/globby/node_modules/ignore": { + "version": "7.0.5", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "cli/node_modules/gopd": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "cli/node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/has-property-descriptors": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-property-descriptors/node_modules/es-define-property": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/has-property-descriptors/node_modules/get-intrinsic": { + "version": "1.2.4", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-property-descriptors/node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-proto": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-symbols": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-tostringtag": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/has-tostringtag/node_modules/has-symbols": { + "version": "1.0.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/hosted-git-info": { + "version": "4.1.0", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/htmlparser2": { + "version": "8.0.2", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "cli/node_modules/http-cache-semantics": { + "version": "4.1.1", + "extraneous": true, + "license": "BSD-2-Clause" + }, + "cli/node_modules/http-proxy-agent": { + "version": "7.0.2", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/https-proxy-agent": { + "version": "7.0.5", + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/iconv-lite": { + "version": "0.6.3", + "extraneous": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/ieee754": { + "version": "1.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "cli/node_modules/ignore": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "cli/node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "cli/node_modules/index-to-position": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/inherits": { + "version": "2.0.4", + "license": "ISC", + "optional": true + }, + "cli/node_modules/ini": { + "version": "1.3.8", + "license": "ISC", + "optional": true + }, + "cli/node_modules/ip-address": { + "version": "10.1.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "cli/node_modules/is-ci": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "cli/node_modules/is-docker": { + "version": "2.2.1", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/is-extglob": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/is-glob": { + "version": "4.0.3", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/is-it-type": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.16.7", + "globalthis": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "cli/node_modules/is-number": { + "version": "7.0.0", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "cli/node_modules/is-wsl": { + "version": "2.2.0", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "cli/node_modules/istextorbinary": { + "version": "9.5.0", + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "cli/node_modules/jackspeak": { + "version": "4.1.1", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "cli/node_modules/js-yaml": { + "version": "4.1.1", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "cli/node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/json5": { + "version": "2.2.3", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/jsonc-parser": { + "version": "3.2.1", + "license": "MIT" + }, + "cli/node_modules/jsonfile": { + "version": "6.2.0", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "cli/node_modules/jsonwebtoken": { + "version": "9.0.2", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "cli/node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.3", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/just-performance": { + "version": "4.3.0", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/jwa": { + "version": "1.4.2", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "cli/node_modules/jws": { + "version": "3.2.3", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.2", + "safe-buffer": "^5.0.1" + } + }, + "cli/node_modules/keytar": { + "version": "7.9.0", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^4.3.0", + "prebuild-install": "^7.0.1" + } + }, + "cli/node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "cli/node_modules/leven": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "cli/node_modules/limiter": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "just-performance": "4.3.0" + } + }, + "cli/node_modules/linkify-it": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "cli/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/lodash": { + "version": "4.18.1", + "license": "MIT" + }, + "cli/node_modules/lodash.includes": { + "version": "4.3.0", + "license": "MIT" + }, + "cli/node_modules/lodash.isboolean": { + "version": "3.0.3", + "license": "MIT" + }, + "cli/node_modules/lodash.isinteger": { + "version": "4.0.4", + "license": "MIT" + }, + "cli/node_modules/lodash.isnumber": { + "version": "3.0.3", + "license": "MIT" + }, + "cli/node_modules/lodash.isplainobject": { + "version": "4.0.6", + "license": "MIT" + }, + "cli/node_modules/lodash.isstring": { + "version": "4.0.1", + "license": "MIT" + }, + "cli/node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/lodash.once": { + "version": "4.1.1", + "license": "MIT" + }, + "cli/node_modules/lodash.truncate": { + "version": "4.4.2", + "license": "MIT" + }, + "cli/node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/make-fetch-happen": { + "version": "15.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/make-fetch-happen/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/markdown-it": { + "version": "14.1.1", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "cli/node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "cli/node_modules/merge2": { + "version": "1.4.1", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/micromatch": { + "version": "4.0.8", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "cli/node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "cli/node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "cli/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "cli/node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "cli/node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/minimatch": { + "version": "3.1.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "cli/node_modules/minimist": { + "version": "1.2.8", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/minipass": { + "version": "7.1.2", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/minipass-collect": { + "version": "2.0.1", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/minipass-collect/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/minipass-fetch": { + "version": "5.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "cli/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/minipass-flush": { + "version": "1.0.5", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/minipass-pipeline": { + "version": "1.2.4", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/minipass-sized": { + "version": "1.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/minizlib": { + "version": "3.1.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "cli/node_modules/mkdirp-classic": { + "version": "0.5.3", + "license": "MIT", + "optional": true + }, + "cli/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "cli/node_modules/mute-stream": { + "version": "0.0.8", + "license": "ISC" + }, + "cli/node_modules/napi-build-utils": { + "version": "1.0.2", + "license": "MIT", + "optional": true + }, + "cli/node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/negotiator": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "cli/node_modules/node-abi": { + "version": "3.56.0", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/node-addon-api": { + "version": "4.3.0", + "license": "MIT", + "optional": true + }, + "cli/node_modules/node-gyp": { + "version": "12.2.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "cli/node_modules/node-gyp/node_modules/which": { + "version": "6.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/node-sarif-builder": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "cli/node_modules/nopt": { + "version": "9.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/normalize-package-data": { + "version": "6.0.2", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "cli/node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "cli/node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.2.0", + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "cli/node_modules/nth-check": { + "version": "2.1.1", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "cli/node_modules/object-inspect": { + "version": "1.13.4", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/object-keys": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "cli/node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "cli/node_modules/open": { + "version": "8.4.2", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/optionator": { + "version": "0.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "cli/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/p-map": { + "version": "7.0.4", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/package-json-from-dist": { + "version": "1.0.0", + "license": "BlueOak-1.0.0" + }, + "cli/node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/parse-json": { + "version": "8.3.0", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/parse-semver": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "semver": "^5.1.0" + } + }, + "cli/node_modules/parse-semver/node_modules/semver": { + "version": "5.7.2", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "cli/node_modules/parse5": { + "version": "7.1.2", + "license": "MIT", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "cli/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "cli/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/path-scurry": { + "version": "2.0.0", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.1", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "cli/node_modules/path-type": { + "version": "6.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/pend": { + "version": "1.2.0", + "license": "MIT" + }, + "cli/node_modules/picocolors": { + "version": "1.1.1", + "license": "ISC" + }, + "cli/node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "cli/node_modules/pluralize": { + "version": "2.0.0", + "license": "MIT" + }, + "cli/node_modules/prebuild-install": { + "version": "7.1.2", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "cli/node_modules/proc-log": { + "version": "6.1.0", + "extraneous": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/promise-retry": { + "version": "2.0.1", + "extraneous": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/pump": { + "version": "3.0.0", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "cli/node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/punycode.js": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/qs": { + "version": "6.15.0", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/queue-microtask": { + "version": "1.2.3", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "cli/node_modules/rc": { + "version": "1.2.8", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "cli/node_modules/rc-config-loader": { + "version": "4.1.3", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "cli/node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/read": { + "version": "1.0.7", + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "cli/node_modules/read-pkg": { + "version": "9.0.1", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "cli/node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "cli/node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "cli/node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "cli/node_modules/retry": { + "version": "0.12.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "cli/node_modules/reusify": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "cli/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/rimraf/node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "cli/node_modules/run-parallel": { + "version": "1.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "cli/node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "cli/node_modules/safer-buffer": { + "version": "2.1.2", + "extraneous": true, + "license": "MIT" + }, + "cli/node_modules/sax": { + "version": "1.3.0", + "license": "ISC" + }, + "cli/node_modules/secretlint": { + "version": "10.2.2", + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "cli/node_modules/secretlint/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "cli/node_modules/semver": { + "version": "7.6.0", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/side-channel": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/side-channel-list": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/side-channel-map": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/side-channel-weakmap": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "cli/node_modules/signal-exit": { + "version": "4.1.0", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "cli/node_modules/simple-concat": { + "version": "1.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "cli/node_modules/simple-get": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "cli/node_modules/simple-invariant": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "cli/node_modules/slash": { + "version": "5.1.0", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/slice-ansi": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "cli/node_modules/smart-buffer": { + "version": "4.2.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "cli/node_modules/socks": { + "version": "2.8.7", + "extraneous": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "cli/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "extraneous": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "cli/node_modules/spdx-correct": { + "version": "3.2.0", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "cli/node_modules/spdx-exceptions": { + "version": "2.5.0", + "license": "CC-BY-3.0" + }, + "cli/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "cli/node_modules/spdx-license-ids": { + "version": "3.0.22", + "license": "CC0-1.0" + }, + "cli/node_modules/ssri": { + "version": "13.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/ssri/node_modules/minipass": { + "version": "7.0.4", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "cli/node_modules/stoppable": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "cli/node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "cli/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/structured-source": { + "version": "4.0.0", + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, + "cli/node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "cli/node_modules/supports-hyperlinks": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "cli/node_modules/table": { + "version": "6.9.0", + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "cli/node_modules/table/node_modules/ajv": { + "version": "8.18.0", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "cli/node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "cli/node_modules/tar": { + "version": "7.5.11", + "extraneous": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "cli/node_modules/tar-fs": { + "version": "2.1.4", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "cli/node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "license": "ISC", + "optional": true + }, + "cli/node_modules/tar-stream": { + "version": "2.2.0", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "cli/node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "cli/node_modules/terminal-link": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/text-table": { + "version": "0.2.0", + "license": "MIT" + }, + "cli/node_modules/textextensions": { + "version": "6.11.0", + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "cli/node_modules/tinyglobby": { + "version": "0.2.15", + "extraneous": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "cli/node_modules/tmp": { + "version": "0.2.4", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "cli/node_modules/to-regex-range": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "cli/node_modules/ts-api-utils": { + "version": "1.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "cli/node_modules/tslib": { + "version": "2.7.0", + "license": "0BSD" + }, + "cli/node_modules/tunnel": { + "version": "0.0.6", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "cli/node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "cli/node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "cli/node_modules/type-fest": { + "version": "4.41.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/typed-rest-client": { + "version": "1.8.11", + "license": "MIT", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, + "cli/node_modules/typescript": { + "version": "5.6.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "cli/node_modules/uc.micro": { + "version": "2.1.0", + "license": "MIT" + }, + "cli/node_modules/underscore": { + "version": "1.13.8", + "license": "MIT" + }, + "cli/node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "cli/node_modules/unicorn-magic": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/unique-filename": { + "version": "5.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/unique-slug": { + "version": "6.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "cli/node_modules/universalify": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "cli/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "cli/node_modules/url-join": { + "version": "4.0.1", + "license": "MIT" + }, + "cli/node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT", + "optional": true + }, + "cli/node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "cli/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "cli/node_modules/version-range": { + "version": "4.15.0", + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "cli/node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "cli/node_modules/wrap-ansi": { + "version": "8.1.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "cli/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "cli/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "cli/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "license": "MIT" + }, + "cli/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "cli/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "cli/node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC", + "optional": true + }, + "cli/node_modules/xml2js": { + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "cli/node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "cli/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, + "cli/node_modules/yauzl": { + "version": "2.10.0", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "cli/node_modules/yauzl-promise": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@node-rs/crc32": "^1.7.0", + "is-it-type": "^5.1.2", + "simple-invariant": "^2.0.1" + }, + "engines": { + "node": ">=16" + } + }, + "cli/node_modules/yazl": { + "version": "2.5.1", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "cli/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@fontsource-variable/geist": { + "version": "5.2.9", + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.2.9.tgz", + "integrity": "sha512-TP+QSBG3wxKGPE33CbMy/L0Nu3qvJ6Fy81Yc4LnQ95xH+i+cfEp8fyU8/kfV14YwszxIFPhnoMTbjL71waVpyQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/geist-mono": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/geist-mono/-/geist-mono-5.2.8.tgz", + "integrity": "sha512-KI5bj+hkkRiHttYHmccotUZ80ZuZyai+RwI1d7UId0clkx/jXxlo8qYK8j54WzmpBjtMoEMPyllV7faDcj+6RA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/roboto": { + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.10.tgz", + "integrity": "sha512-8HlA5FtSfz//oFSr2eL7GFXAiE7eIkcGOtx7tjsLKq+as702x9+GU7K95iDeWFapHC4M2hv9RrpXKRTGGBI8Zg==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/openvsx-webui": { + "resolved": "webui", + "link": true + }, + "node_modules/ovsx": { + "resolved": "cli", + "link": true + }, + "webui": { + "name": "openvsx-webui", + "version": "1.1.0-dev.0", + "license": "EPL-2.0", + "dependencies": { + "@emotion/react": "^11.11.1", + "@emotion/styled": "^11.11.0", + "@fontsource-variable/geist": "^5.2.9", + "@fontsource-variable/geist-mono": "^5.2.8", + "@fontsource/roboto": "^5.2.10", + "@mdit/plugin-alert": "^0.22.3", + "@mui/base": "^5.0.0-beta.9", + "@mui/icons-material": "^5.15.14", + "@mui/material": "^5.15.14", + "@mui/system": "^5.15.14", + "@mui/x-charts": "^8.28.2", + "@mui/x-data-grid": "^8.28.2", + "@mui/x-date-pickers": "^8.27.2", + "@tanstack/react-query": "^5.101.0", + "@tanstack/react-query-devtools": "^5.101.0", + "clipboard-copy": "^4.0.1", + "clsx": "^1.2.1", + "dompurify": "^3.0.4", + "fetch-retry": "^5.0.6", + "lodash": "^4.17.21", + "luxon": "^3.7.2", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^9.2.0", + "prop-types": "^15.8.1", + "punycode": "^2.3.0", + "react": "^18.2.0", + "react-avatar-editor": "^15.0.0", + "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-helmet-async": "^2.0.5", + "react-infinite-scroller": "^1.2.6", + "react-router": "^6.30.4", + "react-router-dom": "^6.30.4" + }, + "devDependencies": { + "@eslint/eslintrc": "^3.3.3", + "@eslint/js": "^9.39.0", + "@playwright/test": "^1.58.0", + "@types/d3-scale": "^4.0", + "@types/d3-shape": "^3.1", + "@types/dompurify": "^3.0.2", + "@types/express": "^4.17.21", + "@types/lodash": "^4.14.195", + "@types/luxon": "^3.7.1", + "@types/markdown-it": "^14.1.0", + "@types/node": "^22.0.0", + "@types/prop-types": "^15.7.0", + "@types/punycode": "^2.1.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@types/react-infinite-scroller": "^1.2.0", + "@types/react-router-dom": "^5.3.0", + "@types/react-transition-group": "^4.4.0", + "@typescript-eslint/eslint-plugin": "^8.56.0", + "@typescript-eslint/parser": "^8.56.0", + "@vitejs/plugin-react": "^5.1.0", + "eslint": "^9.39.0", + "eslint-config-prettier": "^10.1.8", + "eslint-parser-plain": "^0.1.1", + "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-refresh": "^0.5.2", + "express": "^4.21.0", + "express-rate-limit": "^7.4.0", + "prettier": "^3.8.4", + "rimraf": "^6.1.0", + "rollup-plugin-visualizer": "^7.0.1", + "ts-node": "^10.9.2", + "typescript": "^5.9.0", + "vite": "^7.3.0", + "vitest": "^3.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "webui/node_modules/@babel/code-frame": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/compat-data": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/core": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "webui/node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@babel/core/node_modules/debug": { + "version": "4.3.6", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/@babel/generator": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "webui/node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "webui/node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-module-imports": { + "version": "7.24.6", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.24.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-module-imports/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-module-imports/node_modules/@babel/types": { + "version": "7.24.6", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.6", + "@babel/helper-validator-identifier": "^7.24.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "webui/node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-string-parser": { + "version": "7.24.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/helpers": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/highlight": { + "version": "7.24.6", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.6", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/highlight/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/highlight/node_modules/picocolors": { + "version": "1.0.1", + "license": "ISC" + }, + "webui/node_modules/@babel/parser": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "webui/node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "webui/node_modules/@babel/runtime": { + "version": "7.28.4", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/template": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/traverse": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/types": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@babel/types/node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "webui/node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "webui/node_modules/@emotion/babel-plugin/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@emotion/cache": { + "version": "11.14.0", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "webui/node_modules/@emotion/hash": { + "version": "0.9.2", + "license": "MIT" + }, + "webui/node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "webui/node_modules/@emotion/memoize": { + "version": "0.9.0", + "license": "MIT" + }, + "webui/node_modules/@emotion/react": { + "version": "11.14.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@emotion/react/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@emotion/serialize": { + "version": "1.3.3", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "webui/node_modules/@emotion/sheet": { + "version": "1.4.0", + "license": "MIT" + }, + "webui/node_modules/@emotion/styled": { + "version": "11.14.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@emotion/styled/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@emotion/unitless": { + "version": "0.10.0", + "license": "MIT" + }, + "webui/node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "webui/node_modules/@emotion/utils": { + "version": "1.4.2", + "license": "MIT" + }, + "webui/node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "license": "MIT" + }, + "webui/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "webui/node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "webui/node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "webui/node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "webui/node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "webui/node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "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.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/@eslint/js": { + "version": "9.39.3", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "webui/node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "webui/node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "webui/node_modules/@floating-ui/core": { + "version": "1.7.3", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "webui/node_modules/@floating-ui/dom": { + "version": "1.7.4", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "webui/node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "webui/node_modules/@floating-ui/utils": { + "version": "0.2.10", + "license": "MIT" + }, + "webui/node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "webui/node_modules/@humanfs/node": { + "version": "0.16.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "webui/node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "webui/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "webui/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "webui/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "webui/node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "webui/node_modules/@jridgewell/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "webui/node_modules/@mdit/plugin-alert": { + "version": "0.22.3", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.1.2" + }, + "peerDependencies": { + "markdown-it": "^14.1.0" + }, + "peerDependenciesMeta": { + "markdown-it": { + "optional": true + } + } + }, + "webui/node_modules/@mui/base": { + "version": "5.0.0-dev.20240529-082515-213b5e33ab", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.6", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14-dev.20240529-082515-213b5e33ab", + "@mui/utils": "^6.0.0-dev.20240529-082515-213b5e33ab", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/base/node_modules/@mui/types": { + "version": "7.4.8", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/base/node_modules/@mui/types/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/base/node_modules/@mui/utils": { + "version": "6.4.9", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "~7.2.24", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/base/node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.2.24", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/base/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/core-downloads-tracker": { + "version": "5.18.0", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "webui/node_modules/@mui/icons-material": { + "version": "5.18.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/material": { + "version": "5.18.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.18.0", + "@mui/system": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/material/node_modules/@mui/utils": { + "version": "5.17.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/material/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/private-theming": { + "version": "5.17.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.17.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/private-theming/node_modules/@mui/utils": { + "version": "5.17.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/private-theming/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/styled-engine": { + "version": "5.18.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "webui/node_modules/@mui/system": { + "version": "5.18.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.17.1", + "@mui/styled-engine": "^5.18.0", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/system/node_modules/@mui/utils": { + "version": "5.17.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/system/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/types": { + "version": "7.2.24", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/utils": { + "version": "7.3.10", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/utils/node_modules/@babel/runtime": { + "version": "7.29.2", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.4.12", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "webui/node_modules/@mui/utils/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/utils/node_modules/react-is": { + "version": "19.2.3", + "license": "MIT" + }, + "webui/node_modules/@mui/x-charts": { + "version": "8.28.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-charts-vendor": "8.26.0", + "@mui/x-internal-gestures": "0.4.0", + "@mui/x-internals": "8.26.0", + "bezier-easing": "^2.1.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "webui/node_modules/@mui/x-charts-vendor": { + "version": "8.26.0", + "license": "MIT AND ISC", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@types/d3-array": "^3.2.2", + "@types/d3-color": "^3.1.3", + "@types/d3-format": "^3.0.4", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.1", + "@types/d3-scale": "^4.0.9", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-time-format": "^4.0.3", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "flatqueue": "^3.0.0", + "internmap": "^2.0.3" + } + }, + "webui/node_modules/@mui/x-charts-vendor/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-charts/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-charts/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/x-data-grid": { + "version": "8.28.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.26.0", + "@mui/x-virtualizer": "0.3.4", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "webui/node_modules/@mui/x-data-grid/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-data-grid/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/x-date-pickers": { + "version": "8.27.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.26.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "webui/node_modules/@mui/x-date-pickers/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-date-pickers/node_modules/clsx": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/@mui/x-internal-gestures": { + "version": "0.4.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + } + }, + "webui/node_modules/@mui/x-internal-gestures/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-internals": { + "version": "8.26.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "webui/node_modules/@mui/x-internals/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@mui/x-virtualizer": { + "version": "0.3.4", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "webui/node_modules/@mui/x-virtualizer/node_modules/@babel/runtime": { + "version": "7.28.6", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@npmcli/agent": { + "version": "4.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/@npmcli/fs": { + "version": "5.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/@npmcli/fs/node_modules/semver": { + "version": "7.6.2", + "extraneous": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/@pkgr/core": { + "version": "0.3.6", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "webui/node_modules/@playwright/test": { + "version": "1.58.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/@popperjs/core": { + "version": "2.11.8", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "webui/node_modules/@remix-run/router": { + "version": "1.23.3", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "webui/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "webui/node_modules/@tanstack/query-core": { + "version": "5.101.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "webui/node_modules/@tanstack/query-devtools": { + "version": "5.101.0", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "webui/node_modules/@tanstack/react-query": { + "version": "5.101.0", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "webui/node_modules/@tanstack/react-query-devtools": { + "version": "5.101.0", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.101.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.101.0", + "react": "^18 || ^19" + } + }, + "webui/node_modules/@tsconfig/node10": { + "version": "1.0.12", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@tsconfig/node12": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@tsconfig/node14": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@tsconfig/node16": { + "version": "1.0.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "webui/node_modules/@types/babel__core/node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__core/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__core/node_modules/@babel/parser": { + "version": "7.25.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@types/babel__core/node_modules/@babel/types": { + "version": "7.25.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__generator": { + "version": "7.6.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "webui/node_modules/@types/babel__generator/node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__generator/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__generator/node_modules/@babel/types": { + "version": "7.25.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "webui/node_modules/@types/babel__template/node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__template/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__template/node_modules/@babel/parser": { + "version": "7.25.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.2" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/@types/babel__template/node_modules/@babel/types": { + "version": "7.25.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__traverse": { + "version": "7.20.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "webui/node_modules/@types/babel__traverse/node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__traverse/node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/babel__traverse/node_modules/@babel/types": { + "version": "7.25.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/@types/body-parser": { + "version": "1.19.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "webui/node_modules/@types/body-parser/node_modules/@types/node": { + "version": "20.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "webui/node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "webui/node_modules/@types/connect": { + "version": "3.4.38", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "webui/node_modules/@types/connect/node_modules/@types/node": { + "version": "20.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "webui/node_modules/@types/d3-array": { + "version": "3.2.2", + "license": "MIT" + }, + "webui/node_modules/@types/d3-color": { + "version": "3.1.3", + "license": "MIT" + }, + "webui/node_modules/@types/d3-format": { + "version": "3.0.4", + "license": "MIT" + }, + "webui/node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "webui/node_modules/@types/d3-path": { + "version": "3.1.1", + "license": "MIT" + }, + "webui/node_modules/@types/d3-scale": { + "version": "4.0.9", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "webui/node_modules/@types/d3-shape": { + "version": "3.1.8", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "webui/node_modules/@types/d3-time": { + "version": "3.0.4", + "license": "MIT" + }, + "webui/node_modules/@types/d3-time-format": { + "version": "4.0.3", + "license": "MIT" + }, + "webui/node_modules/@types/d3-timer": { + "version": "3.0.2", + "license": "MIT" + }, + "webui/node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/dompurify": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "webui/node_modules/@types/estree": { + "version": "1.0.6", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/express": { + "version": "4.17.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "webui/node_modules/@types/express-serve-static-core": { + "version": "4.19.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "webui/node_modules/@types/express-serve-static-core/node_modules/@types/node": { + "version": "20.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "webui/node_modules/@types/history": { + "version": "4.7.11", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/http-errors": { + "version": "2.0.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/linkify-it": { + "version": "5.0.0", + "license": "MIT" + }, + "webui/node_modules/@types/lodash": { + "version": "4.17.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/luxon": { + "version": "3.7.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/markdown-it": { + "version": "14.1.2", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "webui/node_modules/@types/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "webui/node_modules/@types/mime": { + "version": "1.3.5", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/node": { + "version": "22.19.11", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "webui/node_modules/@types/node/node_modules/undici-types": { + "version": "6.21.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/parse-json": { + "version": "4.0.2", + "license": "MIT" + }, + "webui/node_modules/@types/prop-types": { + "version": "15.7.15", + "license": "MIT" + }, + "webui/node_modules/@types/punycode": { + "version": "2.1.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/qs": { + "version": "6.9.15", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/range-parser": { + "version": "1.2.7", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@types/react": { + "version": "18.3.26", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "webui/node_modules/@types/react-dom": { + "version": "18.3.7", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "webui/node_modules/@types/react-infinite-scroller": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "webui/node_modules/@types/react-router": { + "version": "5.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "webui/node_modules/@types/react-router-dom": { + "version": "5.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "webui/node_modules/@types/react-transition-group": { + "version": "4.4.12", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "webui/node_modules/@types/send": { + "version": "0.17.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "webui/node_modules/@types/send/node_modules/@types/node": { + "version": "20.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "webui/node_modules/@types/serve-static": { + "version": "1.15.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "webui/node_modules/@types/serve-static/node_modules/@types/node": { + "version": "20.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "webui/node_modules/@types/trusted-types": { + "version": "2.0.7", + "devOptional": true, + "license": "MIT" + }, + "webui/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/type-utils": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "webui/node_modules/@typescript-eslint/parser": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/parser/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/@typescript-eslint/parser/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@typescript-eslint/project-service": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.0", + "@typescript-eslint/types": "^8.56.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/project-service/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/@typescript-eslint/project-service/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "webui/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/type-utils": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0", + "@typescript-eslint/utils": "8.56.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/type-utils/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/@typescript-eslint/type-utils/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@typescript-eslint/types": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "webui/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.0", + "@typescript-eslint/tsconfig-utils": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/visitor-keys": "8.56.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "webui/node_modules/@typescript-eslint/typescript-estree/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.9", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/@typescript-eslint/typescript-estree/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/@typescript-eslint/utils": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.0", + "@typescript-eslint/types": "8.56.0", + "@typescript-eslint/typescript-estree": "8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "webui/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "webui/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "webui/node_modules/@vitest/expect": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/@vitest/mocker": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.6", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "webui/node_modules/@vitest/pretty-format": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/@vitest/runner": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.6", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/@vitest/snapshot": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/@vitest/spy": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/@vitest/utils": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.6", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/abbrev": { + "version": "4.0.0", + "extraneous": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/accepts": { + "version": "1.3.8", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/acorn": { + "version": "8.14.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "webui/node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "webui/node_modules/acorn-walk": { + "version": "8.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "webui/node_modules/acorn-walk/node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "webui/node_modules/agent-base": { + "version": "7.1.1", + "extraneous": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "webui/node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "webui/node_modules/ansi-regex": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "webui/node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "webui/node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array-flatten": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/array-includes": { + "version": "3.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.findlast": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flat": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/arraybuffer.prototype.slice/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/arraybuffer.prototype.slice/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/arraybuffer.prototype.slice/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/arraybuffer.prototype.slice/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-buffer/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-length/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract": { + "version": "1.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-data-view/node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-abstract/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-set-tostringtag/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-set-tostringtag/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-set-tostringtag/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-set-tostringtag/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/get-symbol-description/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/get-symbol-description/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/get-symbol-description/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/get-symbol-description/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-regex/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/object.assign": { + "version": "4.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/regexp.prototype.flags/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-array-concat/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-array-concat/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-array-concat/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-array-concat/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-buffer/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-length/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-length/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-length/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-offset/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-offset/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-byte-offset/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/typed-array-length/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/unbox-primitive": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/which-typed-array": { + "version": "1.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/which-typed-array/node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.flatmap/node_modules/which-typed-array/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/async-function": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/async-generator-function": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/attr-accept": { + "version": "2.2.5", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/babel-plugin-macros": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "webui/node_modules/babel-plugin-macros/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "webui/node_modules/bezier-easing": { + "version": "2.1.0", + "license": "MIT" + }, + "webui/node_modules/body-parser": { + "version": "1.20.4", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "webui/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "webui/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "webui/node_modules/browserslist": { + "version": "4.28.1", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "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" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "webui/node_modules/bundle-name": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/bytes": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/cac": { + "version": "6.7.14", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/cacache": { + "version": "20.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^5.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/call-bound": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/call-bound/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/call-bound/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/call-bound/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/call-bound/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/callsites": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/caniuse-lite": { + "version": "1.0.30001769", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "webui/node_modules/chai": { + "version": "5.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/chalk": { + "version": "2.4.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "webui/node_modules/check-error": { + "version": "2.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "webui/node_modules/chownr": { + "version": "3.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/clipboard-copy": { + "version": "4.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "webui/node_modules/cliui": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "webui/node_modules/clsx": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "webui/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "webui/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/content-disposition": { + "version": "0.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/content-type": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/convert-source-map": { + "version": "1.9.0", + "license": "MIT" + }, + "webui/node_modules/cookie": { + "version": "0.7.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/cookie-signature": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/cosmiconfig": { + "version": "7.1.0", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "webui/node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "webui/node_modules/csstype": { + "version": "3.1.3", + "license": "MIT" + }, + "webui/node_modules/d3-array": { + "version": "3.2.4", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-color": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-format": { + "version": "3.1.2", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-interpolate": { + "version": "3.0.1", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-path": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-scale": { + "version": "4.0.2", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-shape": { + "version": "3.2.0", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-time": { + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-time-format": { + "version": "4.1.0", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/d3-timer": { + "version": "3.0.1", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/data-view-buffer": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/data-view-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/data-view-byte-offset": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/debug": { + "version": "4.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/deep-eql": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/default-browser": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/default-browser-id": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/define-data-property": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/define-lazy-prop": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/depd": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/destroy": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "webui/node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "webui/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/dom-helpers": { + "version": "5.2.1", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "webui/node_modules/dom-helpers/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/dompurify": { + "version": "3.4.11", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "webui/node_modules/dunder-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/dunder-proto/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/ee-first": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/electron-to-chromium": { + "version": "1.5.286", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/emoji-regex": { + "version": "10.6.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/encodeurl": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/encoding": { + "version": "0.1.13", + "extraneous": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "webui/node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "extraneous": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/entities": { + "version": "4.5.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "webui/node_modules/env-paths": { + "version": "2.2.1", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/err-code": { + "version": "2.0.3", + "extraneous": true, + "license": "MIT" + }, + "webui/node_modules/error-ex": { + "version": "1.3.2", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "webui/node_modules/es-abstract": { + "version": "1.23.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-abstract/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-abstract/node_modules/internal-slot": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-abstract/node_modules/object-inspect": { + "version": "1.13.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-abstract/node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-abstract/node_modules/side-channel": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/call-bind/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/call-bind/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/data-view-buffer/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/data-view-byte-length/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract": { + "version": "1.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-data-view/node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/get-intrinsic/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/get-intrinsic/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/object.assign": { + "version": "4.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-buffer/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-byte-length/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-byte-offset/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/typed-array-length/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/unbox-primitive": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/which-typed-array": { + "version": "1.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-iterator-helpers/node_modules/which-typed-array/node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/es-module-lexer": { + "version": "1.7.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/es-object-atoms": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-set-tostringtag": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/es-shim-unscopables": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "webui/node_modules/es-to-primitive": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/esbuild": { + "version": "0.27.3", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "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" + } + }, + "webui/node_modules/escalade": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/escape-html": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/escape-string-regexp": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/eslint": { + "version": "9.39.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@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.12.4", + "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.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "webui/node_modules/eslint-config-prettier": { + "version": "10.1.8", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "webui/node_modules/eslint-parser-plain": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/eslint-plugin-prettier": { + "version": "5.5.6", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.13" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "webui/node_modules/eslint-plugin-react": { + "version": "7.37.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "webui/node_modules/eslint-plugin-react-refresh": { + "version": "0.5.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "webui/node_modules/eslint-plugin-react/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/eslint-plugin-react/node_modules/object.values": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "webui/node_modules/eslint/node_modules/acorn": { + "version": "8.16.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "webui/node_modules/eslint/node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "webui/node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "webui/node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "webui/node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "webui/node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/eslint/node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/espree": { + "version": "10.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "webui/node_modules/esquery": { + "version": "1.7.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "webui/node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "webui/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "webui/node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "webui/node_modules/estree-walker/node_modules/@types/estree": { + "version": "1.0.9", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/etag": { + "version": "1.8.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "webui/node_modules/exponential-backoff": { + "version": "3.1.1", + "extraneous": true, + "license": "Apache-2.0" + }, + "webui/node_modules/express": { + "version": "4.22.1", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "webui/node_modules/express-rate-limit": { + "version": "7.5.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "webui/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "webui/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "webui/node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "webui/node_modules/fetch-retry": { + "version": "5.0.6", + "license": "MIT" + }, + "webui/node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "webui/node_modules/file-selector": { + "version": "2.1.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.7.0" + }, + "engines": { + "node": ">= 12" + } + }, + "webui/node_modules/finalhandler": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "webui/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/find-root": { + "version": "1.1.0", + "license": "MIT" + }, + "webui/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "webui/node_modules/flatqueue": { + "version": "3.0.0", + "license": "ISC" + }, + "webui/node_modules/flatted": { + "version": "3.4.2", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/for-each": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "webui/node_modules/forwarded": { + "version": "0.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/fresh": { + "version": "0.5.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/fs-minipass": { + "version": "3.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "webui/node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "webui/node_modules/function-bind": { + "version": "1.1.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/function.prototype.name": { + "version": "1.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/generator-function": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "webui/node_modules/get-east-asian-width": { + "version": "1.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/get-intrinsic/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/get-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/get-symbol-description": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/glob": { + "version": "13.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "webui/node_modules/glob/node_modules/balanced-match": { + "version": "4.0.4", + "extraneous": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "webui/node_modules/glob/node_modules/brace-expansion": { + "version": "5.0.3", + "extraneous": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "webui/node_modules/glob/node_modules/minimatch": { + "version": "10.2.4", + "extraneous": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/globalthis": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/graceful-fs": { + "version": "4.2.11", + "extraneous": true, + "license": "ISC" + }, + "webui/node_modules/has-bigints": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/has-symbols": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/has-tostringtag": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/has-tostringtag/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/hasown": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "webui/node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "webui/node_modules/http-cache-semantics": { + "version": "4.1.1", + "extraneous": true, + "license": "BSD-2-Clause" + }, + "webui/node_modules/http-errors": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "webui/node_modules/http-proxy-agent": { + "version": "7.0.2", + "extraneous": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "webui/node_modules/https-proxy-agent": { + "version": "7.0.4", + "extraneous": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "webui/node_modules/iconv-lite": { + "version": "0.4.24", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/ignore": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "webui/node_modules/import-fresh": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "webui/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/internal-slot": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/internmap": { + "version": "2.0.3", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/invariant": { + "version": "2.2.4", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "webui/node_modules/ip-address": { + "version": "9.0.5", + "extraneous": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "webui/node_modules/ipaddr.js": { + "version": "1.9.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "webui/node_modules/is-array-buffer": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-arrayish": { + "version": "0.2.1", + "license": "MIT" + }, + "webui/node_modules/is-async-function": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-bigint": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-boolean-object": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-callable": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-core-module": { + "version": "2.13.1", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-data-view": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-date-object": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-docker": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/is-finalizationregistry": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-generator-function": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/is-in-ssh": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/is-inside-container": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/is-map": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-negative-zero": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-number-object": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-regex": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-set": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-string": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-symbol": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-symbol/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-weakmap": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-weakref": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-weakset": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/is-wsl": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/isexe": { + "version": "3.1.1", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "webui/node_modules/iterator.prototype": { + "version": "1.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/iterator.prototype/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/iterator.prototype/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/iterator.prototype/node_modules/get-intrinsic/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/iterator.prototype/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "webui/node_modules/js-yaml": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "webui/node_modules/jsbn": { + "version": "1.1.0", + "extraneous": true, + "license": "MIT" + }, + "webui/node_modules/jsesc": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "license": "MIT" + }, + "webui/node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/json5": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/jsx-ast-utils": { + "version": "3.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "webui/node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "webui/node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/lines-and-columns": { + "version": "1.2.4", + "license": "MIT" + }, + "webui/node_modules/linkify-it": { + "version": "5.0.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "webui/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/lodash": { + "version": "4.18.1", + "license": "MIT" + }, + "webui/node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "webui/node_modules/loupe": { + "version": "3.2.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/lru-cache": { + "version": "11.2.5", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "webui/node_modules/luxon": { + "version": "3.7.2", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "webui/node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "webui/node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/make-error": { + "version": "1.3.6", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/make-fetch-happen": { + "version": "15.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^5.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "ssri": "^13.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/markdown-it": { + "version": "14.2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/markdown-it" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.1", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "webui/node_modules/markdown-it-anchor": { + "version": "9.2.0", + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "webui/node_modules/math-intrinsics": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/mdurl": { + "version": "2.0.0", + "license": "MIT" + }, + "webui/node_modules/media-typer": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/merge-descriptors": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/methods": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/mime": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "webui/node_modules/minipass": { + "version": "7.1.2", + "extraneous": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "webui/node_modules/minipass-collect": { + "version": "2.0.1", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "webui/node_modules/minipass-fetch": { + "version": "5.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "webui/node_modules/minipass-flush": { + "version": "1.0.5", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "webui/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/minipass-pipeline": { + "version": "1.2.4", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/minipass-sized": { + "version": "1.0.3", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "extraneous": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/minizlib": { + "version": "3.1.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "webui/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "webui/node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/negotiator": { + "version": "1.0.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/node-gyp": { + "version": "12.2.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/node-gyp/node_modules/semver": { + "version": "7.6.2", + "extraneous": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/node-releases": { + "version": "2.0.27", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/nopt": { + "version": "9.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/object-inspect": { + "version": "1.13.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/object.assign": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/object.assign/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/object.entries": { + "version": "1.1.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/object.entries/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/object.entries/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/object.fromentries": { + "version": "2.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/object.values": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/on-finished": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/open": { + "version": "11.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/own-keys": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/own-keys/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/own-keys/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/own-keys/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/own-keys/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/p-map": { + "version": "7.0.4", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "webui/node_modules/parent-module": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/parse-json": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/parse-json/node_modules/@babel/code-frame": { + "version": "7.24.6", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.24.6", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/parse-json/node_modules/picocolors": { + "version": "1.0.1", + "license": "ISC" + }, + "webui/node_modules/parseurl": { + "version": "1.3.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/path-parse": { + "version": "1.0.7", + "license": "MIT" + }, + "webui/node_modules/path-scurry": { + "version": "2.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.2", + "extraneous": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "webui/node_modules/path-to-regexp": { + "version": "0.1.13", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/path-type": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/pathval": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "webui/node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "webui/node_modules/playwright": { + "version": "1.58.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "webui/node_modules/playwright-core": { + "version": "1.58.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "webui/node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/postcss": { + "version": "8.5.10", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "webui/node_modules/powershell-utils": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/prettier": { + "version": "3.8.4", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "webui/node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "webui/node_modules/proc-log": { + "version": "6.1.0", + "extraneous": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/promise-retry": { + "version": "2.0.1", + "extraneous": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "webui/node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "webui/node_modules/proxy-addr": { + "version": "2.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "webui/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/punycode.js": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/qs": { + "version": "6.15.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/range-parser": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/raw-body": { + "version": "2.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/react": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/react-avatar-editor": { + "version": "15.1.0", + "license": "MIT", + "peerDependencies": { + "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "webui/node_modules/react-dom": { + "version": "18.3.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "webui/node_modules/react-dropzone": { + "version": "14.3.8", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.4", + "file-selector": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "webui/node_modules/react-fast-compare": { + "version": "3.2.2", + "license": "MIT" + }, + "webui/node_modules/react-helmet-async": { + "version": "2.0.5", + "license": "Apache-2.0", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0" + } + }, + "webui/node_modules/react-infinite-scroller": { + "version": "1.2.6", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "webui/node_modules/react-is": { + "version": "19.2.4", + "license": "MIT" + }, + "webui/node_modules/react-refresh": { + "version": "0.18.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/react-router": { + "version": "6.30.4", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "webui/node_modules/react-router-dom": { + "version": "6.30.4", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.3", + "react-router": "6.30.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "webui/node_modules/react-transition-group": { + "version": "4.4.5", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "webui/node_modules/react-transition-group/node_modules/@babel/runtime": { + "version": "7.26.10", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "webui/node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/call-bind/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/data-view-buffer/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/data-view-byte-length/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract": { + "version": "1.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-data-view/node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-abstract/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/get-intrinsic/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/get-intrinsic/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/get-intrinsic/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-regex/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/object.assign": { + "version": "4.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/regexp.prototype.flags/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-buffer/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-length/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-length/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-length/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-offset/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-offset/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-byte-offset/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-length/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-length/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/typed-array-length/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/unbox-primitive": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/which-typed-array": { + "version": "1.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/which-typed-array/node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reflect.getprototypeof/node_modules/which-typed-array/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "webui/node_modules/regexp.prototype.flags": { + "version": "1.5.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/reselect": { + "version": "5.1.1", + "license": "MIT" + }, + "webui/node_modules/resolve": { + "version": "1.22.8", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/resolve-from": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/retry": { + "version": "0.12.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "webui/node_modules/rimraf": { + "version": "6.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/rimraf/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "webui/node_modules/rimraf/node_modules/brace-expansion": { + "version": "5.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "webui/node_modules/rimraf/node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/rimraf/node_modules/lru-cache": { + "version": "11.0.2", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "webui/node_modules/rimraf/node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/rimraf/node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "webui/node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "webui/node_modules/rimraf/node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "webui/node_modules/rollup": { + "version": "4.60.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "webui/node_modules/rollup-plugin-visualizer": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^11.0.0", + "picomatch": "^4.0.2", + "source-map": "^0.7.4", + "yargs": "^18.0.0" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "rolldown": "1.x || ^1.0.0-beta || ^1.0.0-rc", + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + } + } + }, + "webui/node_modules/rollup-plugin-visualizer/node_modules/source-map": { + "version": "0.7.6", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "webui/node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/run-applescript": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/safe-array-concat": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/safe-array-concat/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "webui/node_modules/safe-push-apply": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/safe-regex-test": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/scheduler": { + "version": "0.23.2", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "webui/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "webui/node_modules/send": { + "version": "0.19.2", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "webui/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/serve-static": { + "version": "1.16.3", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/set-function-length": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/set-function-name": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/set-proto": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/setprototypeof": { + "version": "1.2.0", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/shallowequal": { + "version": "1.1.0", + "license": "MIT" + }, + "webui/node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/side-channel": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-list": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-map": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-map/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/side-channel-map/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/side-channel-map/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-map/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-weakmap": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-weakmap/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/side-channel-weakmap/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/side-channel-weakmap/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/side-channel-weakmap/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "webui/node_modules/smart-buffer": { + "version": "4.2.0", + "extraneous": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "webui/node_modules/socks": { + "version": "2.8.3", + "extraneous": true, + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "webui/node_modules/socks-proxy-agent": { + "version": "8.0.3", + "extraneous": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "webui/node_modules/source-map": { + "version": "0.5.7", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/sprintf-js": { + "version": "1.1.3", + "extraneous": true, + "license": "BSD-3-Clause" + }, + "webui/node_modules/ssri": { + "version": "13.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/statuses": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/std-env": { + "version": "3.10.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string-width": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/string.prototype.matchall": { + "version": "4.0.12", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/call-bind/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/data-view-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/data-view-buffer/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/data-view-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/data-view-byte-length/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/data-view-byte-offset": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract": { + "version": "1.24.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-array-buffer": { + "version": "3.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/call-bind/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/es-define-property/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/gopd/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-data-view/node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-abstract/node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-set-tostringtag": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/es-to-primitive": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/function.prototype.name": { + "version": "1.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/get-intrinsic": { + "version": "1.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "async-generator-function": "^1.0.0", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/get-intrinsic/node_modules/es-define-property": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/get-intrinsic/node_modules/es-object-atoms": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/get-symbol-description": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/is-weakref": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/object-inspect": { + "version": "1.13.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/object.assign": { + "version": "4.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/safe-array-concat": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/string.prototype.trim": { + "version": "1.2.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/string.prototype.trimend": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-buffer/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-length": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-length/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-length/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-offset/node_modules/has-proto": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-byte-offset/node_modules/is-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-length": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-length/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-length/node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-length/node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/typed-array-length/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/unbox-primitive": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/which-typed-array": { + "version": "1.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.matchall/node_modules/which-typed-array/node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.repeat": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "webui/node_modules/string.prototype.repeat/node_modules/es-abstract": { + "version": "1.23.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.3", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.repeat/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.repeat/node_modules/internal-slot": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/string.prototype.repeat/node_modules/side-channel": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.repeat/node_modules/side-channel/node_modules/object-inspect": { + "version": "1.13.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.trim": { + "version": "1.2.9", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.trimend": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/strip-ansi": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "webui/node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/strip-literal": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "webui/node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/stylis": { + "version": "4.2.0", + "license": "MIT" + }, + "webui/node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/synckit": { + "version": "0.11.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "webui/node_modules/tar": { + "version": "7.5.16", + "extraneous": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "extraneous": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "webui/node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/tinyexec": { + "version": "0.3.2", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/tinyglobby": { + "version": "0.2.15", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "webui/node_modules/tinypool": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "webui/node_modules/tinyrainbow": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "webui/node_modules/tinyspy": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "webui/node_modules/to-fast-properties": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "webui/node_modules/toidentifier": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "webui/node_modules/ts-api-utils": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "webui/node_modules/ts-node": { + "version": "10.9.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "webui/node_modules/ts-node/node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "webui/node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "webui/node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "webui/node_modules/type-is": { + "version": "1.6.18", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "webui/node_modules/typed-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "webui/node_modules/typed-array-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/typed-array-length": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "webui/node_modules/uc.micro": { + "version": "2.1.0", + "license": "MIT" + }, + "webui/node_modules/unbox-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/unbox-primitive/node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/unbox-primitive/node_modules/which-boxed-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/unique-filename": { + "version": "5.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/unique-slug": { + "version": "6.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/unpipe": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/update-browserslist-db": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "webui/node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "webui/node_modules/use-sync-external-store": { + "version": "1.6.0", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "webui/node_modules/utils-merge": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "webui/node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/vary": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "webui/node_modules/vite": { + "version": "7.3.5", + "dev": true, + "license": "MIT", + "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" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "webui/node_modules/vite-node": { + "version": "3.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "webui/node_modules/vite-node/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/vitest": { + "version": "3.2.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.6", + "@vitest/mocker": "3.2.6", + "@vitest/pretty-format": "^3.2.6", + "@vitest/runner": "3.2.6", + "@vitest/snapshot": "3.2.6", + "@vitest/spy": "3.2.6", + "@vitest/utils": "3.2.6", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.6", + "@vitest/ui": "3.2.6", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "webui/node_modules/vitest/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "webui/node_modules/vitest/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "webui/node_modules/vitest/node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "webui/node_modules/which": { + "version": "6.0.0", + "extraneous": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "webui/node_modules/which-boxed-primitive": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-bigint": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-boolean-object": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-number-object": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-string": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/is-symbol": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-boxed-primitive/node_modules/safe-regex-test": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/call-bind": { + "version": "1.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/for-each": { + "version": "0.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/gopd": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/is-date-object": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/is-regex": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-builtin-type/node_modules/which-typed-array": { + "version": "1.1.20", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-collection": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/which-typed-array": { + "version": "1.1.15", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "webui/node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "webui/node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "webui/node_modules/wrap-ansi": { + "version": "9.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "webui/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "webui/node_modules/wsl-utils": { + "version": "0.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "webui/node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "webui/node_modules/yallist": { + "version": "4.0.0", + "extraneous": true, + "license": "ISC" + }, + "webui/node_modules/yaml": { + "version": "1.10.3", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "webui/node_modules/yargs": { + "version": "18.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "webui/node_modules/yargs-parser": { + "version": "22.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "webui/node_modules/yargs/node_modules/escalade": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "webui/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/webui/index.html b/webui/index.html index 0a097be03..821ee92b4 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3,9 +3,6 @@ - - - diff --git a/webui/package.json b/webui/package.json index c32d2edf0..eb43f9128 100644 --- a/webui/package.json +++ b/webui/package.json @@ -41,6 +41,9 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", + "@fontsource-variable/geist": "^5.2.9", + "@fontsource-variable/geist-mono": "^5.2.8", + "@fontsource/roboto": "^5.2.10", "@mdit/plugin-alert": "^0.22.3", "@mui/base": "^5.0.0-beta.9", "@mui/icons-material": "^5.15.14", diff --git a/webui/src/components/categories.ts b/webui/src/components/categories.ts new file mode 100644 index 000000000..3d6e0f0e3 --- /dev/null +++ b/webui/src/components/categories.ts @@ -0,0 +1,55 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { SvgIconProps } from '@mui/material'; +import DataObjectIcon from '@mui/icons-material/DataObject'; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; +import SpellcheckIcon from '@mui/icons-material/Spellcheck'; +import FormatAlignLeftIcon from '@mui/icons-material/FormatAlignLeft'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import PaletteIcon from '@mui/icons-material/Palette'; +import BugReportIcon from '@mui/icons-material/BugReport'; +import ScienceIcon from '@mui/icons-material/Science'; +import AccountTreeIcon from '@mui/icons-material/AccountTree'; +import KeyboardIcon from '@mui/icons-material/Keyboard'; +import ExtensionIcon from '@mui/icons-material/Extension'; +import BarChartIcon from '@mui/icons-material/BarChart'; +import TranslateIcon from '@mui/icons-material/Translate'; +import SchoolIcon from '@mui/icons-material/School'; +import InsightsIcon from '@mui/icons-material/Insights'; +import GridViewIcon from '@mui/icons-material/GridView'; + +export interface Category { + label: string; + icon: FunctionComponent; +} + +/** Edit this array to add, remove, or reorder categories. */ +export const CATEGORIES: Category[] = [ + { label: 'Programming Languages', icon: DataObjectIcon }, + { label: 'AI', icon: AutoAwesomeIcon }, + { label: 'Linters', icon: SpellcheckIcon }, + { label: 'Formatters', icon: FormatAlignLeftIcon }, + { label: 'Snippets', icon: ContentCopyIcon }, + { label: 'Themes', icon: PaletteIcon }, + { label: 'Debuggers', icon: BugReportIcon }, + { label: 'Testing', icon: ScienceIcon }, + { label: 'SCM Providers', icon: AccountTreeIcon }, + { label: 'Keymaps', icon: KeyboardIcon }, + { label: 'Extension Packs', icon: ExtensionIcon }, + { label: 'Visualization', icon: BarChartIcon }, + { label: 'Language Packs', icon: TranslateIcon }, + { label: 'Education', icon: SchoolIcon }, + { label: 'Data Science', icon: InsightsIcon }, + { label: 'Other', icon: GridViewIcon } +]; + +export const DefaultCategoryIcon: FunctionComponent = GridViewIcon; + +export const CATEGORY_ICONS: Record> = Object.fromEntries( + CATEGORIES.map(c => [c.label, c.icon]) +); diff --git a/webui/src/components/category-card.tsx b/webui/src/components/category-card.tsx new file mode 100644 index 000000000..391faa88b --- /dev/null +++ b/webui/src/components/category-card.tsx @@ -0,0 +1,57 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { Box, ButtonBase, SvgIconProps, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; + +const Root = styled(ButtonBase)(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + justifyContent: 'flex-start', + textAlign: 'left', + overflow: 'hidden', + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadiusCard, + padding: '18px', + color: theme.palette.text.primary, + width: '100%', + transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', + '&:hover': { + borderColor: theme.palette.secondary.main, + boxShadow: 'var(--shadow)', + transform: 'translateY(-2px)' + } +})); + +export interface CategoryCardProps { + label: string; + icon: FunctionComponent; + onClick: () => void; +} + +export const CategoryCard: FunctionComponent = ({ label, icon: Icon, onClick }) => ( + + + + + {label} + +); diff --git a/webui/src/components/category-list-item.tsx b/webui/src/components/category-list-item.tsx new file mode 100644 index 000000000..0fea9ee5b --- /dev/null +++ b/webui/src/components/category-list-item.tsx @@ -0,0 +1,55 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { ButtonBase, SvgIconProps } from '@mui/material'; +import { styled } from '@mui/material/styles'; + +const Root = styled(ButtonBase, { + shouldForwardProp: prop => prop !== 'isSelected' +})<{ isSelected: boolean }>(({ theme, isSelected }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + gap: '8px', + width: '100%', + textAlign: 'left', + padding: '7px 10px', + borderRadius: theme.shape.borderRadius, + overflow: 'hidden', + fontSize: '13.5px', + fontWeight: isSelected ? 600 : 400, + color: isSelected ? theme.palette.secondary.light : theme.palette.text.secondary, + backgroundColor: isSelected ? theme.palette.accentSoft : 'transparent', + marginBottom: '1px', + fontFamily: 'inherit', + transition: 'background 0.14s, color 0.14s', + '&:hover': isSelected + ? {} + : { + backgroundColor: theme.palette.surface3, + color: theme.palette.text.primary + } +})); + +export interface CategoryListItemProps { + label: string; + icon: FunctionComponent; + isSelected: boolean; + onClick: () => void; +} + +export const CategoryListItem: FunctionComponent = ({ + label, + icon: Icon, + isSelected, + onClick +}) => ( + + + {label} + +); diff --git a/webui/src/components/category-pill.tsx b/webui/src/components/category-pill.tsx new file mode 100644 index 000000000..ac13fc674 --- /dev/null +++ b/webui/src/components/category-pill.tsx @@ -0,0 +1,49 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { ButtonBase, SvgIconProps } from '@mui/material'; +import { styled } from '@mui/material/styles'; + +const Root = styled(ButtonBase, { + shouldForwardProp: prop => prop !== 'isSelected' +})<{ isSelected?: boolean }>(({ theme, isSelected }) => ({ + display: 'inline-flex', + alignItems: 'center', + gap: '7px', + flexShrink: 0, + overflow: 'hidden', + backgroundColor: isSelected ? theme.palette.accentSoft : theme.palette.surface2, + border: `1px solid ${isSelected ? theme.palette.secondary.main : theme.palette.divider}`, + color: isSelected ? theme.palette.secondary.light : theme.palette.text.secondary, + fontSize: '13px', + fontWeight: isSelected ? 600 : 500, + padding: '7px 13px', + borderRadius: '999px', + whiteSpace: 'nowrap', + fontFamily: 'inherit', + transition: 'border-color 0.14s, color 0.14s', + '&:hover': isSelected + ? {} + : { + borderColor: theme.palette.secondary.main, + color: theme.palette.secondary.light + } +})); + +export interface CategoryPillProps { + label: string; + icon: FunctionComponent; + isSelected?: boolean; + onClick: () => void; +} + +export const CategoryPill: FunctionComponent = ({ label, icon: Icon, isSelected, onClick }) => ( + + + {label} + +); diff --git a/webui/src/components/extension-card.tsx b/webui/src/components/extension-card.tsx new file mode 100644 index 000000000..0821439f1 --- /dev/null +++ b/webui/src/components/extension-card.tsx @@ -0,0 +1,197 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useContext, useState, useEffect, useRef } from 'react'; +import { flushSync } from 'react-dom'; +import { Link as RouteLink, useNavigate } from 'react-router-dom'; +import { Paper, Typography, Box, Fade } from '@mui/material'; +import { styled, alpha } from '@mui/material/styles'; +import SaveAltIcon from '@mui/icons-material/SaveAlt'; +import { MainContext } from '../context'; +import { ExtensionDetailRoutes } from '../pages/extension-detail/extension-detail-routes'; +import { SearchEntry } from '../extension-registry-types'; +import { ExtensionRatingStars } from '../pages/extension-detail/extension-rating-stars'; +import { createRoute } from '../utils'; +import { MONO_FONT } from '../default/theme'; + +const CardRoot = styled(Paper)(({ theme }) => ({ + padding: '22px 16px', + [theme.breakpoints.down('sm')]: { padding: '14px 10px' }, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + textAlign: 'center', + height: '100%', + minHeight: '206px', + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadiusCard, + backgroundColor: theme.palette.background.paper, + cursor: 'pointer', + transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', + '&:hover': { + borderColor: theme.palette.secondary.main, + boxShadow: 'var(--shadow)', + transform: 'translateY(-2px)' + }, + // Keyboard focus ring — mirrors the search field's :focus-within style + 'a:focus-visible &': { + borderColor: theme.palette.secondary.main, + boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}` + } +})); + +export const ExtensionCard: FunctionComponent = props => { + const [icon, setIcon] = useState(); + const context = useContext(MainContext); + const abortController = useRef(new AbortController()); + + useEffect(() => { + updateChanges(); + return () => { + abortController.current.abort(); + if (icon) URL.revokeObjectURL(icon); + }; + }, []); + + useEffect(() => { + updateChanges(); + }, [props.extension.namespace, props.extension.name, props.extension.version]); + + const updateChanges = async (): Promise => { + if (icon) URL.revokeObjectURL(icon); + try { + const icon = await context.service.getExtensionIcon(abortController.current, props.extension); + setIcon(icon); + } catch (err) { + context.handleError(err); + } + }; + + const { extension, filterSize, idx } = props; + const navigate = useNavigate(); + const route = createRoute([ExtensionDetailRoutes.ROOT, extension.namespace, extension.name]); + const numberFormat = new Intl.NumberFormat(undefined, { notation: 'compact', compactDisplay: 'short' } as any); + const downloadCountFormatted = numberFormat.format(extension.downloadCount ?? 0); + const vtName = `ext-${extension.namespace}-${extension.name}`.replace(/[^a-zA-Z0-9-]/g, '-'); + + const handleCardClick = (e: React.MouseEvent) => { + if (!('startViewTransition' in document)) return; + e.preventDefault(); + const img = (e.currentTarget as HTMLElement).querySelector('img'); + if (img) img.style.viewTransitionName = vtName; + (document as any).startViewTransition(() => { + flushSync(() => navigate(route)); + }); + }; + + return ( + + + + + + + + + {extension.displayName ?? extension.name} + + + + {extension.namespace} + + + {extension.version} + + + + + + + {downloadCountFormatted !== '0' && ( + + + {downloadCountFormatted} + + )} + + + + + + ); +}; + +export interface ExtensionCardProps { + extension: SearchEntry; + idx: number; + filterSize: number; +} diff --git a/webui/src/pages/extension-list/extension-list.tsx b/webui/src/components/extension-list.tsx similarity index 82% rename from webui/src/pages/extension-list/extension-list.tsx rename to webui/src/components/extension-list.tsx index b30d5a0fd..14e34834b 100644 --- a/webui/src/pages/extension-list/extension-list.tsx +++ b/webui/src/components/extension-list.tsx @@ -1,22 +1,19 @@ -/******************************************************************************** - * Copyright (c) 2019 TypeFox and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. * * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ + *****************************************************************************/ import { FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroller'; -import { Box, Grid, CircularProgress, Container } from '@mui/material'; -import { ExtensionListItem } from './extension-list-item'; -import { isError, SearchEntry, SearchResult } from '../../extension-registry-types'; -import { ExtensionFilter } from '../../extension-registry-service'; -import { debounce } from '../../utils'; -import { DelayedLoadIndicator } from '../../components/delayed-load-indicator'; -import { MainContext } from '../../context'; +import { Box, CircularProgress } from '@mui/material'; +import { ExtensionCard } from './extension-card'; +import { isError, SearchEntry, SearchResult } from '../extension-registry-types'; +import { ExtensionFilter } from '../extension-registry-service'; +import { debounce } from '../utils'; +import { DelayedLoadIndicator } from './delayed-load-indicator'; +import { useGridKeyboardNavigation } from './use-grid-keyboard-navigation'; +import { MainContext } from '../context'; export const ExtensionList: FunctionComponent = props => { const abortController = useRef(new AbortController()); @@ -31,6 +28,7 @@ export const ExtensionList: FunctionComponent = props => { const [appliedFilter, setAppliedFilter] = useState(); const [hasMore, setHasMore] = useState(false); const [loading, setLoading] = useState(true); + const grid = useGridKeyboardNavigation(); useEffect(() => { enableLoadMore.current = true; @@ -96,7 +94,6 @@ export const ExtensionList: FunctionComponent = props => { extensionKeys.forEach(key => newExtensionKeys.add(key)); const searchResult = result as SearchResult; if (enableLoadMore.current && isSameFilter(props.filter, filter)) { - // Check for duplicate keys to avoid problems due to asynchronous user edit / loadMore call for (const ext of searchResult.extensions) { const key = `${ext.namespace}.${ext.name}`; if (!extensionKeys.has(key)) { @@ -137,12 +134,7 @@ export const ExtensionList: FunctionComponent = props => { }; const extensionList = extensions.map((ext, idx) => ( - + )); const loader = ( @@ -155,11 +147,17 @@ export const ExtensionList: FunctionComponent = props => { <> - - - {extensionList} - - + + {extensionList} + ); diff --git a/webui/src/components/extension-searchfield.tsx b/webui/src/components/extension-searchfield.tsx new file mode 100644 index 000000000..8ceec0225 --- /dev/null +++ b/webui/src/components/extension-searchfield.tsx @@ -0,0 +1,144 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { ChangeEvent, FunctionComponent, KeyboardEvent, useRef } from 'react'; +import SearchIcon from '@mui/icons-material/Search'; +import ClearIcon from '@mui/icons-material/Close'; +import { IconButton, InputBase, Box } from '@mui/material'; +import { styled, alpha } from '@mui/material/styles'; +import { MONO_FONT } from '../default/theme'; + +interface ExtensionSearchfieldProps { + onSearchChanged: (s: string) => void; + onSearchSubmit?: (s: string) => void; + searchQuery?: string; + placeholder: string; + hideIconButton?: boolean; + error?: boolean; + autoFocus?: boolean; + viewTransitionName?: string; + inputProps?: object; +} + +const SearchWrap = styled(Box, { + shouldForwardProp: prop => prop !== 'hasError' +})<{ hasError?: boolean }>(({ theme, hasError }) => ({ + flex: 2, + display: 'flex', + alignItems: 'center', + gap: '10px', + marginRight: 0, + marginBottom: 0, + [theme.breakpoints.up('md')]: { marginRight: theme.spacing(1), marginBottom: 0 }, + [theme.breakpoints.down('sm')]: { marginBottom: theme.spacing(2) }, + border: hasError ? '2px solid' : '1px solid', + borderColor: hasError ? theme.palette.error.main : theme.palette.divider, + borderRadius: '11px', + height: '52px', + padding: '0 13px', + backgroundColor: theme.palette.surface2, + transition: 'border-color 0.18s, box-shadow 0.18s', + '&:focus-within': { + borderColor: theme.palette.secondary.main, + boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}` + } +})); + +const MonoSlash = styled('span')(({ theme }) => ({ + fontFamily: MONO_FONT, + color: theme.palette.secondary.light, + fontSize: '17px', + lineHeight: 1, + flexShrink: 0, + userSelect: 'none' +})); + +const SearchInput = styled(InputBase)(({ theme }) => ({ + flex: 1, + fontFamily: MONO_FONT, + fontSize: '15px', + color: theme.palette.text.primary, + '& input::placeholder': { color: theme.palette.text.disabled, opacity: 1 }, + '& input::-webkit-search-cancel-button': { display: 'none' } +})); + +export const ExtensionSearchfield: FunctionComponent = props => { + const inputRef = useRef(null); + + const handleSearchChange = (event: ChangeEvent) => { + props.onSearchChanged(event.target.value); + }; + + const handleSearchButtonClick = () => { + if (props.onSearchSubmit) { + props.onSearchSubmit(props.searchQuery ?? ''); + } + }; + + const handleClear = () => { + props.onSearchChanged(''); + inputRef.current?.focus(); + }; + + return ( + + / + { + if (e.key === 'Enter' && props.onSearchSubmit) { + props.onSearchSubmit(props.searchQuery ?? ''); + } + }} + /> + + {props.searchQuery && ( + + + + )} + {!props.hideIconButton && ( + + + + )} + + ); +}; diff --git a/webui/src/components/kbd-key.tsx b/webui/src/components/kbd-key.tsx new file mode 100644 index 000000000..b6f986a0a --- /dev/null +++ b/webui/src/components/kbd-key.tsx @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import { FunctionComponent, PropsWithChildren } from 'react'; +import { Box } from '@mui/material'; +import { MONO_FONT } from '../default/theme'; + +export const KbdKey: FunctionComponent = ({ children }) => ( + + {children} + +); diff --git a/webui/src/components/openvsx-mark.tsx b/webui/src/components/openvsx-mark.tsx new file mode 100644 index 000000000..a1167e99a --- /dev/null +++ b/webui/src/components/openvsx-mark.tsx @@ -0,0 +1,14 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; + +export const OpenVsxMark: FunctionComponent = () => ( + + + + +); diff --git a/webui/src/components/sanitized-markdown.tsx b/webui/src/components/sanitized-markdown.tsx index 09228c535..d6550fd9d 100644 --- a/webui/src/components/sanitized-markdown.tsx +++ b/webui/src/components/sanitized-markdown.tsx @@ -66,6 +66,8 @@ const Markdown = styled('div')(({ theme }: { theme: Theme }) => ({ '& table': { borderCollapse: 'collapse', borderSpacing: 0, + display: 'block', + overflowX: 'auto', '& tr, & td, & th': { border: '1px solid #ddd' }, diff --git a/webui/src/components/shortcuts-modal.tsx b/webui/src/components/shortcuts-modal.tsx new file mode 100644 index 000000000..45a698b8e --- /dev/null +++ b/webui/src/components/shortcuts-modal.tsx @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import { FunctionComponent } from 'react'; +import { Dialog, DialogTitle, DialogContent, Box, Typography, IconButton } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import { KbdKey } from './kbd-key'; +import { useKeyboardShortcuts } from '../keyboard-shortcuts-context'; + +interface ShortcutsModalProps { + open: boolean; + onClose: () => void; +} + +export const ShortcutsModal: FunctionComponent = ({ open, onClose }) => { + const { shortcuts } = useKeyboardShortcuts(); + + return ( + + + Keyboard shortcuts + + + + + + {shortcuts.map((s, i) => ( + + {s.label} + {s.key} + + ))} + + + ); +}; diff --git a/webui/src/components/use-grid-keyboard-navigation.ts b/webui/src/components/use-grid-keyboard-navigation.ts new file mode 100644 index 000000000..1b8b7bbab --- /dev/null +++ b/webui/src/components/use-grid-keyboard-navigation.ts @@ -0,0 +1,141 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FocusEvent, KeyboardEvent, RefObject, useCallback, useEffect, useRef } from 'react'; + +const ITEM_SELECTOR = 'a[data-ext-card]'; +const NAV_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End']; + +function focusSearchInput(): void { + const hero = document.getElementById('hero-search-input') as HTMLInputElement | null; + const nav = document.getElementById('search-input') as HTMLInputElement | null; + const target = hero ?? nav; + if (!target) return; + target.focus(); + requestAnimationFrame(() => target.setSelectionRange(target.value.length, target.value.length)); +} + +interface GridKeyboardNavigation { + containerRef: RefObject; + onKeyDown: (event: KeyboardEvent) => void; + onFocus: (event: FocusEvent) => void; +} + +/** + * Two-axis keyboard navigation for the extension results grid. + * + * Items (card links marked with `data-ext-card`) participate in a roving + * tabindex: only one is tab-focusable at a time. Arrow keys move focus across + * both axes — the column count is derived from the live layout so it adapts to + * viewport width and infinite-scroll appends. Pressing ArrowUp on the first row + * returns focus to the search field. + */ +export function useGridKeyboardNavigation(): GridKeyboardNavigation { + const containerRef = useRef(null); + + const getItems = useCallback((): HTMLElement[] => { + const container = containerRef.current; + if (!container) return []; + return Array.from(container.querySelectorAll(ITEM_SELECTOR)); + }, []); + + // Column count = number of items sharing the topmost row's offsetTop. + const getColumnCount = useCallback((items: HTMLElement[]): number => { + if (items.length === 0) return 1; + const firstTop = items[0].offsetTop; + let cols = 0; + for (const item of items) { + if (item.offsetTop !== firstTop) break; + cols++; + } + return Math.max(cols, 1); + }, []); + + const setRovingTabIndex = useCallback((items: HTMLElement[], activeIndex: number): void => { + items.forEach((item, i) => { + item.tabIndex = i === activeIndex ? 0 : -1; + }); + }, []); + + // Keep exactly one item tab-focusable as the list mounts and grows + // (infinite scroll appends new links that default to tabIndex 0). + useEffect(() => { + const container = containerRef.current; + if (!container) return; + const sync = () => { + const items = getItems(); + if (items.length === 0) return; + const active = items.findIndex(item => item.tabIndex === 0); + setRovingTabIndex(items, active === -1 ? 0 : active); + }; + sync(); + const observer = new MutationObserver(sync); + observer.observe(container, { childList: true, subtree: true }); + return () => observer.disconnect(); + }, [getItems, setRovingTabIndex]); + + const onKeyDown = useCallback( + (event: KeyboardEvent): void => { + if (!NAV_KEYS.includes(event.key)) return; + + const items = getItems(); + if (items.length === 0) return; + + const currentIndex = items.indexOf(document.activeElement as HTMLElement); + if (currentIndex === -1) return; + + const cols = getColumnCount(items); + const last = items.length - 1; + let nextIndex = currentIndex; + + switch (event.key) { + case 'ArrowRight': + nextIndex = Math.min(currentIndex + 1, last); + break; + case 'ArrowLeft': + nextIndex = Math.max(currentIndex - 1, 0); + break; + case 'ArrowDown': + nextIndex = Math.min(currentIndex + cols, last); + break; + case 'ArrowUp': + if (currentIndex < cols) { + event.preventDefault(); + focusSearchInput(); + return; + } + nextIndex = currentIndex - cols; + break; + case 'Home': + nextIndex = 0; + break; + case 'End': + nextIndex = last; + break; + } + + if (nextIndex !== currentIndex) { + event.preventDefault(); + setRovingTabIndex(items, nextIndex); + items[nextIndex].focus(); + } + }, + [getItems, getColumnCount, setRovingTabIndex] + ); + + const onFocus = useCallback( + (event: FocusEvent): void => { + const target = event.target as HTMLElement; + if (!target.matches(ITEM_SELECTOR)) return; + const items = getItems(); + const index = items.indexOf(target); + if (index >= 0) setRovingTabIndex(items, index); + }, + [getItems, setRovingTabIndex] + ); + + return { containerRef, onKeyDown, onFocus }; +} diff --git a/webui/src/default/default-app.tsx b/webui/src/default/default-app.tsx index 995bfdb10..ad1135399 100644 --- a/webui/src/default/default-app.tsx +++ b/webui/src/default/default-app.tsx @@ -8,6 +8,13 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ +import '@fontsource-variable/geist/index.css'; +import '@fontsource-variable/geist-mono/index.css'; +import '@fontsource/roboto/300.css'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; +import '@fontsource/roboto/700.css'; +import '../main.css'; import { createRoot } from 'react-dom/client'; import { useMemo } from 'react'; import { HelmetProvider } from 'react-helmet-async'; diff --git a/webui/src/default/menu-content.tsx b/webui/src/default/menu-content.tsx index e078ad297..a02195dab 100644 --- a/webui/src/default/menu-content.tsx +++ b/webui/src/default/menu-content.tsx @@ -30,8 +30,9 @@ import PublishIcon from '@mui/icons-material/Publish'; import AccountBoxIcon from '@mui/icons-material/AccountBox'; import { UserAvatar } from '../pages/user/avatar'; import { UserSettingsRoutes } from '../pages/user/user-settings-routes'; -import { styled, Theme } from '@mui/material/styles'; +import { alpha, styled, Theme } from '@mui/material/styles'; import { MainContext } from '../context'; +import { KbdKey } from '../components/kbd-key'; import SettingsIcon from '@mui/icons-material/Settings'; import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings'; import LogoutIcon from '@mui/icons-material/Logout'; @@ -67,7 +68,7 @@ export const MobileUserAvatar: FunctionComponent = () => { } return ( - + } aria-controls='user-actions' id='user-avatar'> @@ -172,15 +173,19 @@ export const MobileMenuContent: FunctionComponent = () => { // eslint-disable-next-line react-refresh/only-export-components export const headerItem = ({ theme }: { theme: Theme }) => ({ - margin: theme.spacing(2.5), - color: theme.palette.text.primary, + margin: theme.spacing(0, 0.5), + padding: theme.spacing(1, 1.5), + color: theme.palette.text.secondary, textDecoration: 'none', - fontSize: '1.1rem', + fontSize: '14px', fontFamily: theme.typography.fontFamily, - fontWeight: theme.typography.fontWeightLight, - letterSpacing: 1, + fontWeight: 500, + letterSpacing: 0, + borderRadius: `${theme.shape.borderRadius}px`, + transition: 'background 0.14s, color 0.14s', '&:hover': { - color: theme.palette.secondary.main, + color: theme.palette.text.primary, + backgroundColor: theme.palette.surface3, textDecoration: 'none' } }); @@ -194,15 +199,37 @@ export const DefaultMenuContent: FunctionComponent = () => { const { user, loginProviders } = useContext(MainContext); return ( <> - Documentation + + Documentation + d + Slack Workspace About {loginProviders && ( <> - {user ? ( diff --git a/webui/src/default/page-settings.tsx b/webui/src/default/page-settings.tsx index 2fccc6f7b..b4b875422 100644 --- a/webui/src/default/page-settings.tsx +++ b/webui/src/default/page-settings.tsx @@ -84,9 +84,52 @@ export default function createPageSettings( ); const searchHeader: FunctionComponent = () => ( - - Extensions for VS Code Compatible Editors - + + + + Open-source registry for VS Code–compatible editors + + + Find the right extension, +
+ for any editor. +
+ + Browse community-published extensions. Free, open, and vendor-neutral. + +
); const additionalRoutes: ReactNode = } />; diff --git a/webui/src/default/theme.tsx b/webui/src/default/theme.tsx index 935d9003d..83223f4c3 100644 --- a/webui/src/default/theme.tsx +++ b/webui/src/default/theme.tsx @@ -11,7 +11,8 @@ import { CSSProperties } from 'react'; import { createTheme, Theme } from '@mui/material'; -// Shared type definitions for palette extensions +export const MONO_FONT = "'Geist Mono', monospace"; + type Color = CSSProperties['color']; interface StatusColors { @@ -48,7 +49,6 @@ interface UnenforcedColors { stripe: string; } -// Shared shape for custom palette properties interface CustomPaletteColors { neutral: NeutralColors; textHint: Color; @@ -64,6 +64,12 @@ interface CustomPaletteColors { scanBackground: ScanBackgroundColors; gray: GrayColors; unenforced: UnenforcedColors; + // Surface tiers not covered by MUI's background.paper + surface2: string; + surface3: string; + border2: string; + accentSoft: string; + bg2: string; } declare module '@mui/material/styles/createPalette' { @@ -74,52 +80,88 @@ declare module '@mui/material/styles/createPalette' { textHint: Color; } } + +declare module '@mui/material/styles' { + interface Shape { + borderRadiusCard: number; + } +} + export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { + const dark = themeType === 'dark'; return createTheme({ + typography: { + fontFamily: "'Geist', 'Roboto', system-ui, -apple-system, sans-serif" + }, + shape: { + borderRadius: 9, + borderRadiusCard: 14 + }, palette: { + mode: themeType, + // Standard MUI palette — these replace the matching CSS vars + background: { + default: dark ? '#0c0c11' : '#ffffff', + paper: dark ? '#15151d' : '#ffffff' + }, + text: { + primary: dark ? '#ededf2' : '#16161c', + secondary: dark ? '#b2b2bf' : '#54545f', + disabled: dark ? '#7a7a87' : '#8c8c98' + }, + divider: dark ? '#262630' : '#e9e9ee', primary: { - main: themeType === 'dark' ? '#eeeeee' : '#444', - dark: themeType === 'dark' ? '#f4f4f4' : '#565157' + main: dark ? '#ededf2' : '#16161c', + dark: dark ? '#f4f4f4' : '#0d0d11' }, secondary: { - main: themeType === 'dark' ? '#c160ef' : '#a60ee5', - contrastText: '#edf5ea' + main: dark ? '#a855f7' : '#8b1fd6', + dark: dark ? '#9333ea' : '#7916bd', + light: dark ? '#c084fc' : '#8b1fd6', // accent-fg + contrastText: '#ffffff' }, + // Custom surface tiers + bg2: dark ? '#101016' : '#f7f7f9', + surface2: dark ? '#1a1a23' : '#fafafa', + surface3: dark ? '#20202b' : '#f2f2f5', + border2: dark ? '#1d1d26' : '#f0f0f3', + accentSoft: dark ? '#291a3d' : '#f4e9fd', + // Legacy admin palette neutral: { - light: themeType === 'dark' ? '#000' : '#e6e6e6', - dark: themeType === 'dark' ? '#151515' : '#fff' + light: dark ? '#000' : '#e6e6e6', + dark: dark ? '#151515' : '#fff' }, textHint: 'rgba(0, 0, 0, 0.38)', - checkboxUnchecked: themeType === 'dark' ? 'rgba(255, 255, 255, 0.23)' : 'rgba(0, 0, 0, 0.23)', + checkboxUnchecked: dark ? 'rgba(255, 255, 255, 0.23)' : 'rgba(0, 0, 0, 0.23)', passed: { - dark: themeType === 'dark' ? '#2e5c32' : '#4db052', - light: themeType === 'dark' ? '#a5d6a7' : '#c8e6c9' + dark: dark ? '#2e5c32' : '#4db052', + light: dark ? '#a5d6a7' : '#c8e6c9' }, quarantined: { - dark: themeType === 'dark' ? '#8e5518' : '#e09030', - light: themeType === 'dark' ? '#ffcc80' : '#ffe0b2' + dark: dark ? '#8e5518' : '#e09030', + light: dark ? '#ffcc80' : '#ffe0b2' }, rejected: { - dark: themeType === 'dark' ? '#7d2e2e' : '#d63c3c', - light: themeType === 'dark' ? '#ef9a9a' : '#ffcdd2' + dark: dark ? '#7d2e2e' : '#d63c3c', + light: dark ? '#ef9a9a' : '#ffcdd2' }, errorStatus: { - dark: themeType === 'dark' ? '#5a5a5a' : '#8a8a8a', - light: themeType === 'dark' ? '#b0b0b0' : '#e0e0e0' + dark: dark ? '#5a5a5a' : '#8a8a8a', + light: dark ? '#b0b0b0' : '#e0e0e0' }, allowed: '#4caf50', blocked: '#f44336', review: '#e6a800', selected: { border: '#c160ef', - background: themeType === 'dark' ? '#3d1b4d' : '#f3e5f9', - backgroundHover: themeType === 'dark' ? '#4d2360' : '#e9d5f5', - hover: themeType === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.04)' + background: dark ? '#3d1b4d' : '#f3e5f9', + backgroundHover: dark ? '#4d2360' : '#e9d5f5', + hover: dark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.04)' }, scanBackground: { - default: themeType === 'dark' ? '#1e1e1e' : '#f5f5f5', - light: themeType === 'dark' ? '#2d2d2d' : '#f0f0f0', - dark: themeType === 'dark' ? '#0a0a0a' : '#fafafa' + default: dark ? '#1e1e1e' : '#f5f5f5', + light: dark ? '#2d2d2d' : '#f0f0f0', + dark: dark ? '#0a0a0a' : '#fafafa' }, gray: { start: '#888888', @@ -128,20 +170,128 @@ export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { gradient: 'linear-gradient(90deg, #888888 0%, #cccccc 50%, #888888 100%)' }, unenforced: { - stripe: - themeType === 'dark' - ? 'repeating-linear-gradient(-45deg, transparent, transparent 4px, rgba(255, 255, 255, 0.12) 4px, rgba(255, 255, 255, 0.12) 8px)' - : 'repeating-linear-gradient(-45deg, transparent, transparent 4px, rgba(0, 0, 0, 0.12) 4px, rgba(0, 0, 0, 0.12) 8px)' - }, - mode: themeType + stripe: dark + ? 'repeating-linear-gradient(-45deg, transparent, transparent 4px, rgba(255, 255, 255, 0.12) 4px, rgba(255, 255, 255, 0.12) 8px)' + : 'repeating-linear-gradient(-45deg, transparent, transparent 4px, rgba(0, 0, 0, 0.12) 4px, rgba(0, 0, 0, 0.12) 8px)' + } }, breakpoints: { - values: { - xs: 340, - sm: 550, - md: 800, - lg: 1040, - xl: 1240 + values: { xs: 340, sm: 550, md: 800, lg: 1040, xl: 1240 } + }, + components: { + MuiAccordion: { + styleOverrides: { + root: { + border: 0, + boxShadow: 'none', + background: 'transparent', + '&:before': { display: 'none' } + } + } + }, + MuiTabs: { + styleOverrides: { + indicator: ({ theme }) => ({ + backgroundColor: theme.palette.secondary.main, + height: '2px' + }) + } + }, + MuiTab: { + styleOverrides: { + root: ({ theme }) => ({ + fontSize: '14px', + fontWeight: 600, + textTransform: 'none', + color: theme.palette.text.disabled, + minHeight: '52px', + padding: '15px 16px', + '&.Mui-selected': { color: theme.palette.text.primary } + }) + } + }, + MuiMenu: { + styleOverrides: { + paper: ({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: '14px !important', + boxShadow: 'var(--shadow-lg)', + marginTop: '6px', + backgroundColor: theme.palette.background.paper + }), + list: { padding: '6px' } + } + }, + MuiMenuItem: { + styleOverrides: { + root: { + borderRadius: '9px', + fontSize: '14px', + fontWeight: 500, + minHeight: '36px' + } + } + }, + MuiTypography: { + styleOverrides: { + button: { + textTransform: 'none', + fontWeight: 500, + letterSpacing: 0 + }, + overline: { + textTransform: 'none', + letterSpacing: 0, + lineHeight: 1.4 + } + } + }, + MuiPopover: { + styleOverrides: { + paper: ({ theme }) => ({ + border: `1px solid ${theme.palette.divider}`, + borderRadius: '14px !important', + boxShadow: 'var(--shadow-lg)', + backgroundColor: theme.palette.background.paper + }) + } + }, + MuiDivider: { + styleOverrides: { + root: ({ theme }) => ({ borderColor: theme.palette.divider }) + } + }, + MuiSelect: { + defaultProps: { + MenuProps: { + PaperProps: { + sx: { + border: '1px solid', + borderColor: 'divider', + borderRadius: '14px !important', + boxShadow: 'var(--shadow-lg)', + mt: '4px' + } + } + } + }, + styleOverrides: { + select: { + '&.MuiInputBase-inputSizeSmall': { + paddingTop: '4px', + paddingBottom: '4px', + paddingLeft: '10px', + fontSize: '13px' + } + } + } + }, + MuiOutlinedInput: { + styleOverrides: { + notchedOutline: ({ theme }) => ({ + borderColor: theme.palette.divider + }) + } } } }); diff --git a/webui/src/keyboard-shortcuts-context.tsx b/webui/src/keyboard-shortcuts-context.tsx new file mode 100644 index 000000000..60de90f9b --- /dev/null +++ b/webui/src/keyboard-shortcuts-context.tsx @@ -0,0 +1,138 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import { + createContext, + FunctionComponent, + PropsWithChildren, + useCallback, + useContext, + useEffect, + useRef, + useState +} from 'react'; + +export interface ShortcutInfo { + key: string; + label: string; + order: number; +} + +interface ShortcutEntry extends ShortcutInfo { + callback: () => void; +} + +interface KeyboardShortcutsContextValue { + shortcuts: ShortcutInfo[]; + register: (entry: ShortcutEntry) => void; + unregister: (key: string) => void; +} + +const KeyboardShortcutsContext = createContext({ + shortcuts: [], + register: () => {}, + unregister: () => {} +}); + +// eslint-disable-next-line react-refresh/only-export-components +export const useKeyboardShortcuts = () => useContext(KeyboardShortcutsContext); + +export const KeyboardShortcutsProvider: FunctionComponent = ({ children }) => { + const registryRef = useRef>(new Map()); + const [shortcuts, setShortcuts] = useState([]); + + const sync = () => + setShortcuts( + Array.from(registryRef.current.values()) + .map(({ key, label, order }) => ({ key, label, order })) + .sort((a, b) => a.order - b.order) + ); + + const register = useCallback((entry: ShortcutEntry) => { + registryRef.current.set(entry.key, entry); + sync(); + }, []); + + const unregister = useCallback((key: string) => { + registryRef.current.delete(key); + sync(); + }, []); + + // Chord detection: only fire a shortcut when the key was pressed in isolation. + // + // Two subtleties drive the implementation: + // + // 1. Key identity: Shift changes e.key (Shift+/ → e.key='?'), but e.code is + // always the physical key ('Slash'). We track by e.code and store e.key at + // keydown so the correct character is available at keyup even if Shift was + // released in between. + // + // 2. Browser defaults: some browsers handle '/' as a find shortcut on keydown, + // before our keyup fires. When a solo registered key goes down we eagerly + // call e.preventDefault() to block that, then fire the callback on keyup. + // + // 3. Missed keyups: browsers don't always deliver keyup. On macOS a held Meta + // swallows the keyup of the other key (Cmd+C, Cmd+T, …), and losing window + // focus (Alt+Tab, opening a tab) sends the keyup elsewhere. Either leaves + // stale entries in `pressed`/`dirty` that permanently break detection, so we + // flush the transient state on window blur and when Meta is released. + useEffect(() => { + const pressed = new Map(); // code → key captured at keydown + const dirty = new Set(); // codes involved in a chord + + const reset = () => { + pressed.clear(); + dirty.clear(); + }; + + const isInput = (target: EventTarget | null) => { + const el = target as HTMLElement; + return ( + el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || el.isContentEditable + ); + }; + + const onKeydown = (e: KeyboardEvent) => { + if (e.repeat || e.key === 'Shift') return; + if (pressed.size > 0) { + pressed.forEach((_, code) => dirty.add(code)); + dirty.add(e.code); + } else if (!isInput(e.target) && registryRef.current.has(e.key)) { + e.preventDefault(); + } + pressed.set(e.code, e.key); + }; + + const onKeyup = (e: KeyboardEvent) => { + if (e.key === 'Shift') return; + // Meta chords swallow the keyup of the other key, so once Meta is + // released flush anything still held to avoid stale state. + if (e.key === 'Meta') return reset(); + const isDirty = dirty.has(e.code); + const key = pressed.get(e.code) ?? e.key; + pressed.delete(e.code); + dirty.delete(e.code); + if (isDirty || isInput(e.target)) return; + const entry = registryRef.current.get(key); + if (entry) entry.callback(); + }; + + document.addEventListener('keydown', onKeydown); + document.addEventListener('keyup', onKeyup); + window.addEventListener('blur', reset); + return () => { + document.removeEventListener('keydown', onKeydown); + document.removeEventListener('keyup', onKeyup); + window.removeEventListener('blur', reset); + }; + }, []); + + return ( + + {children} + + ); +}; diff --git a/webui/src/layout/app-footer.tsx b/webui/src/layout/app-footer.tsx new file mode 100644 index 000000000..d22b2b48f --- /dev/null +++ b/webui/src/layout/app-footer.tsx @@ -0,0 +1,241 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useContext, useEffect, useState } from 'react'; +import { Box, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { KbdKey } from '../components/kbd-key'; +import { OpenVsxMark } from '../components/openvsx-mark'; +import { MONO_FONT } from '../default/theme'; +import { MainContext } from '../context'; + +const FooterLink = styled('a')(({ theme }) => ({ + fontSize: '13.5px', + color: theme.palette.text.secondary, + textDecoration: 'none', + display: 'block', + '&:hover': { color: theme.palette.secondary.light } +})); + +const FooterColumnHead = styled(Typography)(({ theme }) => ({ + fontSize: '12px', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: '0.08em', + color: theme.palette.text.disabled, + marginBottom: '14px' +})); + +const SocialIconButton = styled('a')(({ theme }) => ({ + width: 34, + height: 34, + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.secondary, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + textDecoration: 'none', + transition: 'border-color 0.14s, color 0.14s', + '&:hover': { + borderColor: theme.palette.secondary.main, + color: theme.palette.secondary.light + } +})); + +const GitHubSvg = () => ( + + + +); + +const LinkedInSvg = () => ( + + + +); + +const XSvg = () => ( + + + +); + +const FOOTER_LINKS = { + resources: [ + { label: 'Documentation', href: 'https://github.com/eclipse/openvsx/wiki' }, + { label: 'API Reference', href: '/swagger-ui.html' }, + { label: 'Publishing Guide', href: 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions' }, + { label: 'Status', href: 'https://status.eclipse.org/' }, + { label: 'Commercial Usage', href: 'https://www.eclipse.org/legal/open-vsx-registry.php' } + ], + community: [ + { label: 'GitHub', href: 'https://github.com/eclipse/openvsx', external: true }, + { label: 'Working Group', href: 'https://openvsxworkinggroup.github.io/', external: true }, + { label: 'Report a Vulnerability', href: 'https://github.com/eclipse/openvsx/security', external: true }, + { + label: 'Slack Workspace', + href: 'https://join.slack.com/t/openvsxworkinggroup/shared_invite/zt-2y07y1ggy-ct3IfJljjGI6xWUQ9llv6A', + external: true + } + ], + legal: [ + { label: 'Privacy Policy', href: 'https://www.eclipse.org/legal/privacy.php', external: true }, + { label: 'Terms of Use', href: 'https://www.eclipse.org/legal/termsofuse.php', external: true }, + { label: 'Security Policy', href: 'https://github.com/eclipse/openvsx/security/policy', external: true }, + { label: 'Eclipse Foundation', href: 'https://www.eclipse.org', external: true } + ] +}; + +const SOCIAL_LINKS = [ + { href: 'https://github.com/eclipse/openvsx', icon: , title: 'GitHub' }, + { href: 'https://www.linkedin.com/company/eclipse-foundation/', icon: , title: 'LinkedIn' }, + { href: 'https://twitter.com/EclipseFdn', icon: , title: 'X (Twitter)' } +]; + +export interface AppFooterProps { + onOpenShortcuts: () => void; +} + +export const AppFooter: FunctionComponent = ({ onOpenShortcuts }) => { + const { service } = useContext(MainContext); + const [version, setVersion] = useState(null); + + useEffect(() => { + const ac = new AbortController(); + service + .getRegistryVersion(ac) + .then(r => setVersion(r.version)) + .catch(() => {}); + return () => ac.abort(); + }, []); + + return ( + + {/* Columns */} + + {/* Brand */} + + + + Open VSX Registry + + + An open-source, vendor-neutral registry for VS Code–compatible extensions. + + + {SOCIAL_LINKS.map(s => ( + + {s.icon} + + ))} + + + {/* Resources */} + + Resources + + {FOOTER_LINKS.resources.map(l => ( + + {l.label} + + ))} + + + {/* Community */} + + Community + + {FOOTER_LINKS.community.map(l => ( + + {l.label} + + ))} + + Keyboard shortcuts + ? + + + + {/* Legal */} + + Legal + + {FOOTER_LINKS.legal.map(l => ( + + {l.label} + + ))} + + + + {/* Bottom bar */} + + + Copyright © Eclipse Foundation, AISBL. All Rights Reserved. + + {version && ( + + v{version} + + )} + + + ); +}; diff --git a/webui/src/layout/app-layout.tsx b/webui/src/layout/app-layout.tsx new file mode 100644 index 000000000..1841ab921 --- /dev/null +++ b/webui/src/layout/app-layout.tsx @@ -0,0 +1,135 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, lazy, Suspense, useContext, useEffect, useState } from 'react'; +import { Routes, Route, useNavigate } from 'react-router-dom'; +import { Box } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { Banner } from '../components/banner'; +import { ShortcutsModal } from '../components/shortcuts-modal'; +import { MainContext } from '../context'; +import { NavSearchProvider } from '../nav-search-context'; +import { KeyboardShortcutsProvider } from '../keyboard-shortcuts-context'; +import { useShortcut } from '../use-shortcut'; +import { getCookieValueByKey, setCookie } from '../utils'; +import { UserData } from '../extension-registry-types'; +import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; +import { UserSettingsRoutes } from '../pages/user/user-settings-routes'; +import { NamespaceDetailRoutes } from '../pages/namespace-detail/namespace-detail-routes'; +import { ExtensionDetailRoutes } from '../pages/extension-detail/extension-detail-routes'; +import { ExtensionDetail } from '../pages/extension-detail/extension-detail'; +import { HomePage } from '../pages/home/home-page'; +import { BrowsePage } from '../pages/browse/browse-page'; +import { NamespaceDetail } from '../pages/namespace-detail/namespace-detail'; +import { NotFound } from '../not-found'; +import { AppNavbar } from './app-navbar'; +import { AppFooter } from './app-footer'; + +const UserSettings = lazy(() => import('../pages/user/user-settings').then(m => ({ default: m.UserSettings }))); + +const Wrapper = styled(Box)({ + display: 'flex', + flexDirection: 'column', + position: 'relative', + minHeight: '100vh' +}); + +const AppLayoutContent: FunctionComponent = props => { + const { pageSettings, loginProviders } = useContext(MainContext); + const { additionalRoutes: AdditionalRoutes, banner: BannerComponent } = pageSettings.elements; + + const navigate = useNavigate(); + const [isBannerOpen, setIsBannerOpen] = useState(false); + const [shortcutsOpen, setShortcutsOpen] = useState(false); + + useEffect(() => { + const banner = pageSettings.elements.banner; + if (banner) { + let open = true; + if (banner.cookie) { + const bannerClosedCookie = getCookieValueByKey(banner.cookie.key); + if (bannerClosedCookie === banner.cookie.value) open = false; + } + setIsBannerOpen(open); + } + }, []); + + useShortcut({ key: '?', label: 'Show keyboard shortcuts', order: 0, callback: () => setShortcutsOpen(true) }); + useShortcut({ + key: 'd', + label: 'Go to documentation', + order: 20, + callback: () => window.open('https://github.com/eclipse/openvsx/wiki', '_blank') + }); + useShortcut({ + key: 'p', + label: 'Publish extension', + order: 30, + callback: () => navigate('/user-settings/extensions'), + enabled: !!loginProviders + }); + + const onDismissBannerButtonClick = () => { + const onClose = pageSettings.elements.banner?.props?.onClose; + if (onClose) onClose(); + const cookie = pageSettings.elements.banner?.cookie; + if (cookie) setCookie(cookie); + setIsBannerOpen(false); + }; + + return ( + + + {BannerComponent ? ( + + + + ) : null} + + + + } /> + } /> + } + /> + } + /> + } /> + } /> + } /> + {AdditionalRoutes ?? null} + } /> + + + + setShortcutsOpen(true)} /> + setShortcutsOpen(false)} /> + + ); +}; + +export const AppLayout: FunctionComponent = props => ( + + + + + +); + +export interface AppLayoutProps { + user?: UserData; + userLoading: boolean; +} diff --git a/webui/src/layout/app-navbar.tsx b/webui/src/layout/app-navbar.tsx new file mode 100644 index 000000000..e1dfec859 --- /dev/null +++ b/webui/src/layout/app-navbar.tsx @@ -0,0 +1,156 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useCallback, useContext, useEffect, useState } from 'react'; +import { flushSync } from 'react-dom'; +import { AppBar, Box, Toolbar } from '@mui/material'; +import { styled, useTheme } from '@mui/material/styles'; +import { Link as RouteLink, useNavigate } from 'react-router-dom'; +import { HeaderMenu } from '../header-menu'; +import { MainContext } from '../context'; +import { useNavSearch } from '../nav-search-context'; +import { OpenVsxMark } from '../components/openvsx-mark'; +import { NavSearchField } from './nav-search-field'; + +const ToolbarItem = styled(Box)({ + display: 'flex', + alignItems: 'center' +}); + +const BLUR_LAYERS = [ + { blur: '22px', sat: 2.0, stops: 'transparent 87.5%, #000 100%' }, + { blur: '14px', sat: 1.7, stops: 'transparent 75%, #000 87.5%, #000 100%' }, + { blur: '8px', sat: 1.45, stops: 'transparent 62.5%, #000 75%, #000 87.5%, transparent 100%' }, + { blur: '5px', sat: 1.4, stops: 'transparent 50%, #000 62.5%, #000 75%, transparent 87.5%' }, + { blur: '3px', sat: 1.35, stops: 'transparent 37.5%, #000 50%, #000 62.5%, transparent 75%' }, + { blur: '2px', sat: 1.3, stops: 'transparent 25%, #000 37.5%, #000 50%, transparent 62.5%' }, + { blur: '1px', sat: 1, stops: 'transparent 12.5%, #000 25%, #000 37.5%, transparent 50%' }, + { blur: '0.5px', sat: 1, stops: '#000 0%, #000 12.5%, #000 25%, transparent 37.5%' } +]; + +export const AppNavbar: FunctionComponent = () => { + const { pageSettings } = useContext(MainContext); + const { toolbarContent: ToolbarContent } = pageSettings.elements; + const { isHeroPage } = useNavSearch(); + const theme = useTheme(); + const navigate = useNavigate(); + const navbg = theme.palette.mode === 'dark' ? 'rgba(14, 14, 20, 0.74)' : 'rgba(255, 255, 255, 0.78)'; + const [scrolled, setScrolled] = useState(false); + + // Trigger the reverse view-transition (nav search → hero) when navigating home + const handleHomeClick = useCallback( + (e: { preventDefault(): void; stopPropagation(): void }) => { + if (isHeroPage) return; + if (!('startViewTransition' in document)) return; + e.preventDefault(); + e.stopPropagation(); + (document as any).startViewTransition(() => { + flushSync(() => navigate('/')); + }); + }, + [isHeroPage, navigate] + ); + + useEffect(() => { + const onScroll = () => setScrolled(window.scrollY > 20); + window.addEventListener('scroll', onScroll, { passive: true }); + return () => window.removeEventListener('scroll', onScroll); + }, []); + + const showSolid = !scrolled; + const showFan = scrolled; + + return ( + + {/* Solid frosted glass — visible when not scrolled */} + + ); +}; diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx new file mode 100644 index 000000000..356e0ad8b --- /dev/null +++ b/webui/src/layout/nav-search-field.tsx @@ -0,0 +1,102 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, KeyboardEvent, useCallback, useRef } from 'react'; +import { Box } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { ExtensionSearchfield } from '../components/extension-searchfield'; +import { useNavSearch } from '../nav-search-context'; +import { useShortcut } from '../use-shortcut'; + +export const NavSearchField: FunctionComponent = () => { + const { isHeroPage, navQuery, setNavQuery, searchHandler } = useNavSearch(); + const navigate = useNavigate(); + + // Keep a ref so handleNavSearch always calls the latest handler without needing + // to be recreated every time searchHandler changes (avoids stale-closure navigates) + const searchHandlerRef = useRef(searchHandler); + searchHandlerRef.current = searchHandler; + + const focusSearch = useCallback(() => { + const hero = document.getElementById('hero-search-input') as HTMLInputElement | null; + if (hero) { + hero.focus(); + // Move cursor to end so the user can continue typing after transitioning back + requestAnimationFrame(() => hero.setSelectionRange(hero.value.length, hero.value.length)); + return; + } + const nav = document.getElementById('search-input') as HTMLInputElement | null; + if (nav) { + nav.focus(); + requestAnimationFrame(() => nav.setSelectionRange(nav.value.length, nav.value.length)); + } + }, []); + + useShortcut({ key: '/', label: 'Focus search', order: 10, callback: focusSearch }); + + const handleNavSearch = useCallback( + (q: string) => { + setNavQuery(q); + if (searchHandlerRef.current) { + searchHandlerRef.current(q); + } else { + navigate(`/browse${q ? '?q=' + encodeURIComponent(q) : ''}`); + } + }, + [navigate, setNavQuery] + ); + + // Move cursor to end when the input gains focus (e.g. after view-transition morphs + // the hero search into this field — browsers select-all by default on programmatic focus) + const handleInputFocus = useCallback((e: { target: HTMLInputElement }) => { + const { target } = e; + requestAnimationFrame(() => target.setSelectionRange(target.value.length, target.value.length)); + }, []); + + // ArrowDown moves focus from the search field into the results grid (first card), + // where two-axis arrow navigation takes over. + const handleInputKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key !== 'ArrowDown') return; + const firstCard = document.querySelector('a[data-ext-card]') as HTMLElement | null; + if (firstCard) { + e.preventDefault(); + firstCard.focus(); + } + }, []); + + return ( + + *': { mb: '0 !important' } + }}> + + + + ); +}; diff --git a/webui/src/main.css b/webui/src/main.css index fff817020..512f631cd 100644 --- a/webui/src/main.css +++ b/webui/src/main.css @@ -1,3 +1,17 @@ +/* Box-shadow tokens: referenced by theme.tsx component overrides and component sx props. + Must be CSS vars (not theme values) because the theme itself uses them in string overrides. */ +:root { + --shadow: 0 1px 2px rgba(20, 20, 40, 0.04), 0 4px 14px rgba(20, 20, 40, 0.05); + --shadow-lg: 0 12px 44px rgba(20, 20, 40, 0.14); +} + +@media (prefers-color-scheme: dark) { + :root { + --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 18px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 16px 54px rgba(0, 0, 0, 0.6); + } +} + html, body { height: 100%; @@ -16,15 +30,59 @@ img { overflow: hidden; } -@keyframes fadein { +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +@keyframes popIn { 0% { opacity: 0; + transform: scale(0.95) translateY(6px); } 100% { opacity: 1; + transform: scale(1) translateY(0); } } +/* View transition for search bar morph */ +::view-transition-old(vt-search) { + animation: 160ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-out; +} +::view-transition-new(vt-search) { + animation: 160ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-in; +} +@keyframes vt-search-out { + to { + opacity: 0; + transform: scale(0.95); + } +} +@keyframes vt-search-in { + from { + opacity: 0; + transform: scale(0.97) translateY(-6px); + } +} + +/* View transition for extension icon card → detail morph */ +::view-transition-old(ext-icon), +::view-transition-new(ext-icon) { + animation-duration: 220ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Root crossfade when navigating between pages */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 160ms; +} + @keyframes fadeout { 0% { opacity: 1; diff --git a/webui/src/main.tsx b/webui/src/main.tsx index 262734e21..74c92392f 100644 --- a/webui/src/main.tsx +++ b/webui/src/main.tsx @@ -22,7 +22,7 @@ import { UserData, isError, ReportedError, isSuccess, LoginProviders } from './e import { MainContext } from './context'; import { PageSettings } from './page-settings'; import { ErrorResponse } from './server-request'; -import { OtherPages } from './other-pages'; +import { AppLayout } from './layout/app-layout'; import '../src/main.css'; @@ -113,7 +113,7 @@ export const Main: FunctionComponent = props => { } /> - } /> + } /> {error ? ( void; + navQuery: string; + setNavQuery: (q: string) => void; + searchHandler: ((q: string) => void) | null; + setSearchHandler: (fn: ((q: string) => void) | null) => void; +} + +const NavSearchContext = createContext({ + isHeroPage: false, + setIsHeroPage: () => {}, + navQuery: '', + setNavQuery: () => {}, + searchHandler: null, + setSearchHandler: () => {} +}); + +export const NavSearchProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { + const [isHeroPage, setIsHeroPage] = useState(false); + const [navQuery, setNavQuery] = useState(''); + // useState treats any function passed directly as a reducer — wrap with () => fn to store functions correctly + const [searchHandler, setSearchHandlerState] = useState<((q: string) => void) | null>(null); + const setSearchHandler = useCallback((fn: ((q: string) => void) | null) => { + setSearchHandlerState(() => fn); + }, []); + return ( + + {children} + + ); +}; + +// eslint-disable-next-line react-refresh/only-export-components +export const useNavSearch = () => useContext(NavSearchContext); diff --git a/webui/src/other-pages.tsx b/webui/src/other-pages.tsx deleted file mode 100644 index eb66835a0..000000000 --- a/webui/src/other-pages.tsx +++ /dev/null @@ -1,157 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * https://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { FunctionComponent, useContext, useEffect, useState, lazy, Suspense } from 'react'; -import { Routes, Route } from 'react-router-dom'; -import { AppBar, Box, Toolbar } from '@mui/material'; -import { styled, Theme } from '@mui/material/styles'; -import { Banner } from './components/banner'; -import { MainContext } from './context'; -import { HeaderMenu } from './header-menu'; -import { ExtensionListRoutes } from './pages/extension-list/extension-list-routes'; -import { UserSettingsRoutes } from './pages/user/user-settings-routes'; -import { NamespaceDetailRoutes } from './pages/namespace-detail/namespace-detail-routes'; -import { ExtensionDetailRoutes } from './pages/extension-detail/extension-detail-routes'; -import { getCookieValueByKey, setCookie } from './utils'; -import { UserData } from './extension-registry-types'; - -import { ExtensionDetail } from './pages/extension-detail/extension-detail'; -import { ExtensionListContainer } from './pages/extension-list/extension-list-container'; -import { NamespaceDetail } from './pages/namespace-detail/namespace-detail'; -import { NotFound } from './not-found'; - -const UserSettings = lazy(() => import('./pages/user/user-settings').then(m => ({ default: m.UserSettings }))); - -const ToolbarItem = styled(Box)({ - display: 'flex', - alignItems: 'center' -}); - -const Wrapper = styled(Box)({ - display: 'flex', - flexDirection: 'column', - position: 'relative', - minHeight: '100vh' -}); - -const Footer = styled('footer')(({ theme }: { theme: Theme }) => ({ - position: 'fixed', - bottom: 0, - width: '100%', - padding: `${theme.spacing(1)} ${theme.spacing(2)}`, - backgroundColor: theme.palette.background.paper, - boxShadow: '0px -2px 6px 0px rgba(0, 0, 0, 0.5)', - backgroundImage: - theme.palette.mode == 'dark' - ? 'linear-gradient(rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.08))' - : undefined -})); - -export const OtherPages: FunctionComponent = props => { - const { pageSettings } = useContext(MainContext); - const { - additionalRoutes: AdditionalRoutes, - banner: BannerComponent, - footer: FooterComponent, - toolbarContent: ToolbarContent - } = pageSettings.elements; - - const [isBannerOpen, setIsBannerOpen] = useState(false); - const [isFooterExpanded, setIsFooterExpanded] = useState(false); - - useEffect(() => { - // Check a cookie to determine whether a banner should be shown - const banner = pageSettings.elements.banner; - if (banner) { - let open = true; - if (banner.cookie) { - const bannerClosedCookie = getCookieValueByKey(banner.cookie.key); - if (bannerClosedCookie === banner.cookie.value) { - open = false; - } - } - setIsBannerOpen(open); - } - }, []); - - const onDismissBannerButtonClick = () => { - const onClose = pageSettings.elements.banner?.props?.onClose; - if (onClose) { - onClose(); - } - const cookie = pageSettings.elements.banner?.cookie; - if (cookie) { - setCookie(cookie); - } - setIsBannerOpen(false); - }; - - const getContentPadding = (): number => { - const footerHeight = pageSettings.elements.footer?.props.footerHeight; - return footerHeight ? footerHeight + 24 : 0; - }; - - return ( - - - - {ToolbarContent ? : null} - - - - - - {BannerComponent ? ( - - - - ) : null} - - - - } /> - } - /> - } - /> - } /> - } /> - } /> - {AdditionalRoutes ?? null} - } /> - - - - {FooterComponent ? ( -
setIsFooterExpanded(true)} onMouseLeave={() => setIsFooterExpanded(false)}> - -
- ) : null} -
- ); -}; - -export interface OtherPagesProps { - user?: UserData; - userLoading: boolean; -} diff --git a/webui/src/pages/browse/browse-header.tsx b/webui/src/pages/browse/browse-header.tsx new file mode 100644 index 000000000..630a36b8b --- /dev/null +++ b/webui/src/pages/browse/browse-header.tsx @@ -0,0 +1,124 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, KeyboardEvent, useState, useEffect } from 'react'; +import { Box, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; +import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; + +export const BrowseHeader: FunctionComponent = props => { + const [sortBy, setSortBy] = useState(props.sortBy); + const [sortOrder, setSortOrder] = useState(props.sortOrder); + + useEffect(() => { + setSortBy(props.sortBy); + setSortOrder(props.sortOrder); + }, [props.sortBy, props.sortOrder]); + + const handleSortByChange = (event: SelectChangeEvent) => { + const sb = event.target.value as SortBy; + setSortBy(sb); + props.onSortByChanged(sb); + }; + + const handleSortOrderChange = () => { + const newOrder = sortOrder === 'asc' ? 'desc' : 'asc'; + setSortOrder(newOrder); + props.onSortOrderChanged(newOrder); + }; + + const title = props.searchQuery ? `"${props.searchQuery}"` : props.category || 'All extensions'; + + return ( + + + + {title} + + + {props.resultNumber.toLocaleString()} extensions found + + + + + Sort by + + + { + if (e.key === 'Enter') { + e.preventDefault(); + handleSortOrderChange(); + } + }} + onClick={handleSortOrderChange}> + {sortOrder === 'asc' ? ( + + ) : ( + + )} + + + + ); +}; + +export interface BrowseHeaderProps { + onSortByChanged: (sb: SortBy) => void; + onSortOrderChanged: (so: SortOrder) => void; + sortBy: SortBy; + sortOrder: SortOrder; + resultNumber: number; + searchQuery?: string; + category?: ExtensionCategory | ''; +} diff --git a/webui/src/pages/browse/browse-page.tsx b/webui/src/pages/browse/browse-page.tsx new file mode 100644 index 000000000..2634504dc --- /dev/null +++ b/webui/src/pages/browse/browse-page.tsx @@ -0,0 +1,142 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useLayoutEffect } from 'react'; +import { Box, Typography } from '@mui/material'; +import { ExtensionCategory } from '../../extension-registry-types'; +import { useNavSearch } from '../../nav-search-context'; +import { ExtensionList } from '../../components/extension-list'; +import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; +import { CategoryPill } from '../../components/category-pill'; +import { CategoryListItem } from '../../components/category-list-item'; +import { useBrowseFilter } from './use-browse-filter'; +import { BrowseHeader } from './browse-header'; + +export const BrowsePage: FunctionComponent = () => { + const { setIsHeroPage } = useNavSearch(); + const { + searchQuery, + category, + sortBy, + sortOrder, + debounceTime, + resultNumber, + categories, + onResultCount, + onCategoryChanged, + onSortByChanged, + onSortOrderChanged + } = useBrowseFilter(); + + useLayoutEffect(() => { + setIsHeroPage(false); + return () => setIsHeroPage(false); + }, []); + + return ( + + {/* Mobile category pills — outside the flex row so negative-margin bleed isn't clipped */} + {categories.length > 0 && ( + + {(['', ...categories] as Array).map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return ( + onCategoryChanged(cat)} + /> + ); + })} + + )} + + + {/* Desktop categories sidebar */} + + + Categories + + {(['', ...categories] as Array).map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return ( + onCategoryChanged(cat)} + /> + ); + })} + + + {/* Main content */} + + + + + + + ); +}; diff --git a/webui/src/pages/browse/use-browse-filter.ts b/webui/src/pages/browse/use-browse-filter.ts new file mode 100644 index 000000000..c2e019088 --- /dev/null +++ b/webui/src/pages/browse/use-browse-filter.ts @@ -0,0 +1,139 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { MainContext } from '../../context'; +import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import { useNavSearch } from '../../nav-search-context'; +import { addQuery } from '../../utils'; +import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; + +function buildBrowseUrl(q: string, cat: ExtensionCategory | '', sb: SortBy, so: SortOrder): string { + const queries: { key: string; value: string }[] = []; + if (q) queries.push({ key: 'q', value: q }); + if (cat) queries.push({ key: 'category', value: cat }); + if (sb && sb !== 'relevance') queries.push({ key: 'sortBy', value: sb }); + if (so && so !== 'desc') queries.push({ key: 'sortOrder', value: so }); + return addQuery(ExtensionListRoutes.BROWSE, queries); +} + +export function useBrowseFilter() { + const { search, state: locationState } = useLocation(); + const context = useContext(MainContext); + const { navQuery, setNavQuery, setSearchHandler } = useNavSearch(); + + // Navigation state is the most reliable fallback for values passed from HomePage + // (URL params may not yet be reflected in useLocation() during view transitions) + const navState = (locationState as { _q?: string; _cat?: string } | null) ?? {}; + + const [searchQuery, setSearchQuery] = useState(() => { + const fromUrl = new URLSearchParams(search).get('q') ?? ''; + return fromUrl || navQuery || navState._q || ''; + }); + const [category, setCategory] = useState(() => { + const fromUrl = (new URLSearchParams(search).get('category') as ExtensionCategory) ?? ''; + return fromUrl || (navState._cat as ExtensionCategory) || ''; + }); + const [sortBy, setSortBy] = useState( + () => (new URLSearchParams(search).get('sortBy') as SortBy) ?? 'relevance' + ); + const [sortOrder, setSortOrder] = useState( + () => (new URLSearchParams(search).get('sortOrder') as SortOrder) ?? 'desc' + ); + const [debounceTime, setDebounceTime] = useState(0); + const [resultNumber, setResultNumber] = useState(0); + const [categories, setCategories] = useState([]); + + // Kept current every render so the stable handler closure always dispatches to latest state + const handlerRef = useRef<(q: string) => void>(() => {}); + handlerRef.current = (q: string) => { + setSearchQuery(q); + setDebounceTime(1000); + history.replaceState(null, '', buildBrowseUrl(q, category, sortBy, sortOrder)); + }; + + // Register this page as the nav bar's search handler; unregister on unmount + useLayoutEffect(() => { + const stable = (q: string) => handlerRef.current(q); + setSearchHandler(stable); + return () => setSearchHandler(null); + }, []); + + // On mount: if the URL has a query but navQuery is empty, push it to context so + // the navbar search bar reflects the current search + useLayoutEffect(() => { + if (!navQuery && searchQuery) setNavQuery(searchQuery); + }, []); + + // Keep local searchQuery in sync when the nav bar drives an external query change. + // Skip the initial mount: searchQuery is already seeded from the URL/navigation state, + // and the layout effect above pushes it into navQuery — reacting here on mount would + // wipe a URL-provided query (e.g. /browse?q=vue) before navQuery has caught up. + const navSyncMounted = useRef(false); + useEffect(() => { + if (!navSyncMounted.current) { + navSyncMounted.current = true; + return; + } + if (navQuery === searchQuery) return; + setSearchQuery(navQuery); + setDebounceTime(500); + history.replaceState(null, '', buildBrowseUrl(navQuery, category, sortBy, sortOrder)); + }, [navQuery]); + + // Load category list once on mount + useEffect(() => { + const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; + cats.sort((a, b) => { + if (a === b) return 0; + if (a === 'Other') return 1; + if (b === 'Other') return -1; + return a.localeCompare(b); + }); + setCategories(cats); + }, []); + + const updateURL = (q: string, cat: ExtensionCategory | '', sb?: SortBy, so?: SortOrder) => { + history.replaceState(null, '', buildBrowseUrl(q, cat, sb ?? sortBy, so ?? sortOrder)); + }; + + const onSearchChanged = (q: string) => { + setSearchQuery(q); + setDebounceTime(1000); + updateURL(q, category); + }; + + const onCategoryChanged = (cat: ExtensionCategory | '') => { + setCategory(cat); + updateURL(searchQuery, cat); + }; + + const onSortByChanged = (sb: SortBy) => { + setSortBy(sb); + updateURL(searchQuery, category, sb, sortOrder); + }; + + const onSortOrderChanged = (so: SortOrder) => { + setSortOrder(so); + updateURL(searchQuery, category, sortBy, so); + }; + + return { + searchQuery, + category, + sortBy, + sortOrder, + debounceTime, + resultNumber, + categories, + onResultCount: setResultNumber, + onSearchChanged, + onCategoryChanged, + onSortByChanged, + onSortOrderChanged + }; +} diff --git a/webui/src/pages/extension-detail/extension-detail-overview.tsx b/webui/src/pages/extension-detail/extension-detail-overview.tsx index 8960c58fc..9be9ec86c 100644 --- a/webui/src/pages/extension-detail/extension-detail-overview.tsx +++ b/webui/src/pages/extension-detail/extension-detail-overview.tsx @@ -343,7 +343,7 @@ export const ExtensionDetailOverview: FunctionComponent - + + - + {extension.displayName ?? extension.name} @@ -304,32 +314,49 @@ const ExtensionHeader: FunctionComponent<{ } const headerTextColor = theme.palette.getContrastText(headerColor); + const usesDefaultBg = !extension.galleryColor; return ( - - + + + - @@ -367,6 +394,32 @@ export const ExtensionDetail: FunctionComponent = () => { [navigate, namespace, name, target] ); + const _basePath = namespace && name ? buildExtensionPath(namespace, name, target) : ''; + const _reviewsPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.REVIEWS) : ''; + const _changesPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.CHANGES) : ''; + + useShortcut({ + key: 'o', + label: 'Extension overview', + order: 40, + callback: () => navigate(_basePath), + enabled: !!_basePath + }); + useShortcut({ + key: 'c', + label: 'Extension changelog', + order: 50, + callback: () => navigate(_changesPath), + enabled: !!_basePath + }); + useShortcut({ + key: 'r', + label: 'Extension reviews', + order: 60, + callback: () => navigate(_reviewsPath), + enabled: !!_basePath + }); + if (!namespace || !name) return null; const basePath = buildExtensionPath(namespace, name, target); @@ -390,17 +443,37 @@ export const ExtensionDetail: FunctionComponent = () => { <> - + + Overviewo + + } component={RouteLink} to={overviewPath} /> - + + Changesc + + } + component={RouteLink} + to={changesPath} + /> + Ratings & Reviewsr + + } component={RouteLink} to={reviewsPath} /> diff --git a/webui/src/pages/extension-list/extension-list-container.tsx b/webui/src/pages/extension-list/extension-list-container.tsx deleted file mode 100644 index b5af72e50..000000000 --- a/webui/src/pages/extension-list/extension-list-container.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 TypeFox and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ - -import { FunctionComponent, useEffect, useState } from 'react'; -import { Box } from '@mui/material'; -import { useLocation } from 'react-router-dom'; -import { addQuery } from '../../utils'; -import { ExtensionCategory, SortOrder, SortBy } from '../../extension-registry-types'; -import { ExtensionList } from './extension-list'; -import { ExtensionListHeader } from './extension-list-header'; - -export const ExtensionListContainer: FunctionComponent = () => { - const [searchQuery, setSearchQuery] = useState(''); - const [category, setCategory] = useState(''); - const [resultNumber, setResultNumber] = useState(0); - const [sortBy, setSortBy] = useState('relevance'); - const [sortOrder, setSortOrder] = useState('desc'); - const [searchDebounceTime, setSearchDebounceTime] = useState(0); - - const { pathname, search } = useLocation(); - - useEffect(() => { - const searchParams = new URLSearchParams(search); - setSearchQuery(searchParams.get('search') ?? ''); - setCategory((searchParams.get('category') as ExtensionCategory) ?? ''); - setSortBy((searchParams.get('sortBy') as SortBy) ?? 'relevance'); - setSortOrder((searchParams.get('sortOrder') as SortOrder) ?? 'desc'); - }, []); - - const onSearchChanged = (searchQuery: string): void => { - setSearchQuery(searchQuery); - setSearchDebounceTime(1000); - updateURL(searchQuery, category, sortBy, sortOrder); - }; - - const onSearchSubmit = (searchQuery: string): void => { - setSearchQuery(searchQuery); - setSearchDebounceTime(0); - }; - - const onCategoryChanged = (category: ExtensionCategory | ''): void => { - setCategory(category); - updateURL(searchQuery, category, sortBy, sortOrder); - }; - - const onSortByChanged = (sortBy: SortBy): void => { - setSortBy(sortBy); - updateURL(searchQuery, category, sortBy, sortOrder); - }; - - const onSortOrderChanged = (sortOrder: SortOrder): void => { - setSortOrder(sortOrder); - updateURL(searchQuery, category, sortBy, sortOrder); - }; - - const updateURL = ( - searchQuery: string, - category: ExtensionCategory | '', - sortBy?: SortBy, - sortOrder?: SortOrder - ): void => { - const queries: { key: string; value: string }[] = []; - if (searchQuery) { - queries.push({ key: 'search', value: searchQuery }); - } - if (category) { - queries.push({ key: 'category', value: category }); - } - if (sortBy) { - queries.push({ key: 'sortBy', value: sortBy }); - } - if (sortOrder) { - queries.push({ key: 'sortOrder', value: sortOrder }); - } - const url = addQuery('', queries) || pathname; - history.replaceState(null, '', url); - }; - - const handleUpdate = (resultNumber: number): void => setResultNumber(resultNumber); - - return ( - - - - - ); -}; diff --git a/webui/src/pages/extension-list/extension-list-header.tsx b/webui/src/pages/extension-list/extension-list-header.tsx deleted file mode 100644 index 793d93885..000000000 --- a/webui/src/pages/extension-list/extension-list-header.tsx +++ /dev/null @@ -1,216 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 TypeFox and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ - -import { ChangeEvent, FunctionComponent, KeyboardEvent, useContext, useEffect, useState } from 'react'; -import { Box, Paper, InputBase, Select, MenuItem, Container, SelectChangeEvent } from '@mui/material'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; -import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; -import { ExtensionListSearchfield } from './extension-list-searchfield'; -import { MainContext } from '../../context'; - -export const ExtensionListHeader: FunctionComponent = props => { - const [categories, setCategories] = useState([]); - const [category, setCategory] = useState(''); - const [sortBy, setSortBy] = useState('relevance'); - const [sortOrder, setSortOrder] = useState('desc'); - const context = useContext(MainContext); - - useEffect(() => { - const categories = Array.from(context.service.getCategories()); - categories.sort((a, b) => { - if (a === b) return 0; - if (a === 'Other') return 1; - if (b === 'Other') return -1; - return a.localeCompare(b); - }); - - setCategories(categories); - setCategory(props.category ?? ''); - setSortBy(props.sortBy); - setSortOrder(props.sortOrder); - }, []); - - useEffect(() => { - setCategory(props.category ?? ''); - setSortBy(props.sortBy); - setSortOrder(props.sortOrder); - }, [props.category, props.sortBy, props.sortOrder]); - - const handleCategoryChange = (event: SelectChangeEvent) => { - const category = (event.target.value as ExtensionCategory) ?? ''; - setCategory(category); - props.onCategoryChanged(category); - }; - - const handleSearchChange = (value: string) => { - props.onSearchChanged(value); - }; - - const handleSearchSubmit = (value: string) => { - props.onSearchSubmit(value); - }; - - const handleSortByChange = (event: ChangeEvent) => { - const sortBy = event.target.value as SortBy; - setSortBy(sortBy); - props.onSortByChanged(sortBy); - }; - - const handleSortOrderChange = () => { - const newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; - setSortOrder(newSortOrder); - props.onSortOrderChanged(newSortOrder); - }; - - const renderValue = (value: string) => { - return value === '' ? ( - - All Categories - - ) : ( - value - ); - }; - - const SearchHeader = context.pageSettings.elements.searchHeader; - return ( - - - {SearchHeader ? : ''} - - - - - - - - - {`${props.resultNumber} Result${props.resultNumber !== 1 ? 's' : ''}`} - - - Sort by - - - { - if (e.key === 'Enter') { - e.preventDefault(); - handleSortOrderChange(); - } - }} - onClick={handleSortOrderChange}> - {sortOrder === 'asc' ? ( - - ) : ( - - )} - - - - - - - ); -}; - -export interface ExtensionListHeaderProps { - onSearchChanged: (s: string) => void; - onSearchSubmit: (s: string) => void; - onCategoryChanged: (c: ExtensionCategory) => void; - onSortByChanged: (sb: SortBy) => void; - onSortOrderChanged: (so: SortOrder) => void; - searchQuery?: string; - category?: ExtensionCategory | ''; - sortBy: SortBy; - sortOrder: SortOrder; - resultNumber: number; -} diff --git a/webui/src/pages/extension-list/extension-list-item.tsx b/webui/src/pages/extension-list/extension-list-item.tsx deleted file mode 100644 index 88e67c8d8..000000000 --- a/webui/src/pages/extension-list/extension-list-item.tsx +++ /dev/null @@ -1,120 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2019 TypeFox and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ - -import { FunctionComponent, useContext, useState, useEffect, useRef } from 'react'; -import { Link as RouteLink } from 'react-router-dom'; -import { Paper, Typography, Box, Grid, Fade } from '@mui/material'; -import SaveAltIcon from '@mui/icons-material/SaveAlt'; -import { MainContext } from '../../context'; -import { ExtensionDetailRoutes } from '../extension-detail/extension-detail-routes'; -import { SearchEntry } from '../../extension-registry-types'; -import { ExtensionRatingStars } from '../extension-detail/extension-rating-stars'; -import { createRoute } from '../../utils'; - -export const ExtensionListItem: FunctionComponent = props => { - const [icon, setIcon] = useState(); - const context = useContext(MainContext); - const abortController = useRef(new AbortController()); - - useEffect(() => { - updateChanges(); - return () => { - abortController.current.abort(); - if (icon) { - URL.revokeObjectURL(icon); - } - }; - }, []); - - useEffect(() => { - updateChanges(); - }, [props.extension.namespace, props.extension.name, props.extension.version]); - - const updateChanges = async (): Promise => { - if (icon) { - URL.revokeObjectURL(icon); - } - try { - const icon = await context.service.getExtensionIcon(abortController.current, props.extension); - setIcon(icon); - } catch (err) { - context.handleError(err); - } - }; - - const { extension, filterSize, idx } = props; - const route = createRoute([ExtensionDetailRoutes.ROOT, extension.namespace, extension.name]); - const numberFormat = new Intl.NumberFormat(undefined, { notation: 'compact', compactDisplay: 'short' } as any); - const downloadCountFormatted = numberFormat.format(extension.downloadCount ?? 0); - return ( - - - - *': { - '&:not(:last-child)': { - marginBottom: '.5rem' - } - }, - opacity: extension.deprecated ? 0.5 : undefined, - filter: extension.deprecated ? 'grayscale(100%)' : undefined - }}> - - - - - - {extension.displayName ?? extension.name} - - - - - {extension.namespace} - - - {extension.version} - - - - -   - {downloadCountFormatted != '0' && ( - <> - {downloadCountFormatted} - - )} - - - - - - ); -}; - -export interface ExtensionListItemProps { - extension: SearchEntry; - idx: number; - filterSize: number; -} diff --git a/webui/src/pages/extension-list/extension-list-routes.ts b/webui/src/pages/extension-list/extension-list-routes.ts index 3ff30749f..6032a091f 100644 --- a/webui/src/pages/extension-list/extension-list-routes.ts +++ b/webui/src/pages/extension-list/extension-list-routes.ts @@ -15,4 +15,5 @@ import { createRoute } from '../../utils'; export namespace ExtensionListRoutes { export const MAIN = createRoute([]); + export const BROWSE = createRoute(['browse']); } diff --git a/webui/src/pages/extension-list/extension-list-searchfield.tsx b/webui/src/pages/extension-list/extension-list-searchfield.tsx deleted file mode 100644 index 371839533..000000000 --- a/webui/src/pages/extension-list/extension-list-searchfield.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2020 TypeFox and others - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License v. 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - ********************************************************************************/ - -import { ChangeEvent, FunctionComponent, KeyboardEvent, useContext } from 'react'; -import SearchIcon from '@mui/icons-material/Search'; -import { Paper, IconButton, InputBase } from '@mui/material'; -import { MainContext } from '../../context'; - -interface ExtensionListSearchfieldProps { - onSearchChanged: (s: string) => void; - onSearchSubmit?: (s: string) => void; - searchQuery?: string; - placeholder: string; - hideIconButton?: boolean; - error?: boolean; - autoFocus?: boolean; -} - -export const ExtensionListSearchfield: FunctionComponent = props => { - const { pageSettings } = useContext(MainContext); - - const handleSearchChange = (event: ChangeEvent) => { - props.onSearchChanged(event.target.value); - }; - - const handleSearchButtonClick = () => { - if (props.onSearchSubmit) { - props.onSearchSubmit(props.searchQuery ?? ''); - } - }; - - const searchIconColor = pageSettings?.themeType === 'dark' ? '#111111' : '#ffffff'; - return ( - - { - if (e.key === 'Enter' && props.onSearchSubmit) { - props.onSearchSubmit(props.searchQuery ?? ''); - } - }} - /> - - {props.hideIconButton ? ( - '' - ) : ( - - - - )} - - ); -}; diff --git a/webui/src/pages/home/home-page.tsx b/webui/src/pages/home/home-page.tsx new file mode 100644 index 000000000..449d1805f --- /dev/null +++ b/webui/src/pages/home/home-page.tsx @@ -0,0 +1,59 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useCallback, useLayoutEffect } from 'react'; +import { flushSync } from 'react-dom'; +import { useNavigate } from 'react-router-dom'; +import { useNavSearch } from '../../nav-search-context'; +import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; +import { addQuery } from '../../utils'; +import { HomepageView } from './homepage-view'; + +export const HomePage: FunctionComponent = () => { + const { setIsHeroPage, setNavQuery } = useNavSearch(); + const navigate = useNavigate(); + + useLayoutEffect(() => { + setIsHeroPage(true); + return () => setIsHeroPage(false); + }, []); + + const focusNavSearch = useCallback(() => { + const input = document.getElementById('search-input') as HTMLInputElement | null; + if (input) { + input.focus(); + input.select(); + } + }, []); + + const handleSearch = useCallback( + (query: string, category?: string) => { + const cat = category ?? ''; + const queries: { key: string; value: string }[] = []; + if (query) queries.push({ key: 'q', value: query }); + if (cat) queries.push({ key: 'category', value: cat }); + const url = addQuery(ExtensionListRoutes.BROWSE, queries); + + if ('startViewTransition' in document) { + const transition = (document as any).startViewTransition(() => { + flushSync(() => { + setNavQuery(query); + setIsHeroPage(false); + navigate(url, { state: { _q: query, _cat: cat } }); + }); + }); + transition.finished.then(focusNavSearch); + } else { + setNavQuery(query); + navigate(url, { state: { _q: query, _cat: cat } }); + requestAnimationFrame(() => requestAnimationFrame(focusNavSearch)); + } + }, + [navigate, setNavQuery, setIsHeroPage, focusNavSearch] + ); + + return ; +}; diff --git a/webui/src/pages/home/homepage-view.tsx b/webui/src/pages/home/homepage-view.tsx new file mode 100644 index 000000000..e2512f027 --- /dev/null +++ b/webui/src/pages/home/homepage-view.tsx @@ -0,0 +1,592 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; +import { Box, ButtonBase, Typography } from '@mui/material'; +import { styled, alpha } from '@mui/material/styles'; +import { MainContext } from '../../context'; +import { useNavSearch } from '../../nav-search-context'; +import { ExtensionCard } from '../../components/extension-card'; +import { SearchEntry, isError } from '../../extension-registry-types'; +import { SortBy, SortOrder } from '../../extension-registry-types'; +import { MONO_FONT } from '../../default/theme'; +import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; +import { CategoryPill } from '../../components/category-pill'; +import { CategoryCard } from '../../components/category-card'; + +const POPULAR_CHIPS = ['python', 'git', 'docker', 'prettier', 'eslint', 'rust', 'java']; + +// ---- Styled components ---- + +const HeroSearchWrap = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: '13px', + backgroundColor: theme.palette.surface2, + border: `1px solid ${theme.palette.divider}`, + borderRadius: '15px', + height: '62px', + paddingLeft: '20px', + paddingRight: '8px', + [theme.breakpoints.down('sm')]: { + height: '54px', + paddingLeft: '14px', + gap: '10px' + }, + boxShadow: 'var(--shadow)', + transition: 'border-color 0.2s ease, box-shadow 0.3s ease', + '&:focus-within': { + borderColor: theme.palette.secondary.main, + boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}, 0 18px 70px -10px ${alpha(theme.palette.secondary.main, 0.45)}` + } +})); + +const HeroInput = styled('input')(({ theme }) => ({ + flex: 1, + height: '100%', + border: 'none', + outline: 'none', + background: 'none', + color: theme.palette.text.primary, + fontSize: '17px', + fontFamily: MONO_FONT, + '&::placeholder': { color: theme.palette.text.disabled } +})); + +const HeroSubmitButton = styled(ButtonBase)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: '8px', + height: '46px', + padding: '0 22px', + borderRadius: '11px', + overflow: 'hidden', + backgroundColor: theme.palette.secondary.main, + color: '#fff', + fontSize: '15px', + fontWeight: 600, + flexShrink: 0, + transition: 'background 0.14s', + [theme.breakpoints.down('sm')]: { + height: '40px', + padding: '0 14px', + borderRadius: '9px' + }, + '&:hover': { backgroundColor: theme.palette.secondary.dark } +})); + +const PopularChip = styled(ButtonBase)(({ theme }) => ({ + backgroundColor: theme.palette.surface2, + border: `1px solid ${theme.palette.divider}`, + color: theme.palette.text.secondary, + fontSize: '13px', + fontWeight: 500, + padding: '6px 13px', + borderRadius: '999px', + overflow: 'hidden', + fontFamily: MONO_FONT, + transition: 'border-color 0.14s, color 0.14s', + '&:hover': { + borderColor: theme.palette.secondary.main, + color: theme.palette.secondary.light + } +})); + +const GetInvolvedCard = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + borderRadius: '16px', + padding: '24px', + display: 'flex', + flexDirection: 'column' +})); + +// ---- Types ---- + +interface CuratedSection { + title: string; + sub: string; + sortBy: SortBy; + extensions: SearchEntry[]; + loading: boolean; +} + +interface HomepageViewProps { + onSearch: (query: string, category?: string) => void; +} + +// ---- Component ---- + +export const HomepageView: FunctionComponent = ({ onSearch }) => { + const context = useContext(MainContext); + const { navQuery } = useNavSearch(); + const [query, setQuery] = useState(() => navQuery); + const [categories, setCategories] = useState([]); + const [sections, setSections] = useState([ + { + title: 'Most Downloaded', + sub: 'The extensions developers rely on every day', + sortBy: 'downloadCount', + extensions: [], + loading: true + }, + { + title: 'Recently Updated', + sub: 'Fresh releases from publishers this week', + sortBy: 'timestamp', + extensions: [], + loading: true + } + ]); + const abortRef = useRef(new AbortController()); + const searchTimerRef = useRef(); + + useEffect(() => { + const EXCLUDED = new Set(['Other', 'SCM Providers', 'Extension Packs']); + const cats = Array.from(context.service.getCategories()) + .filter(c => !EXCLUDED.has(c)) + .sort((a, b) => a.localeCompare(b)); + setCategories(cats); + + const ac = abortRef.current; + sections.forEach((section, idx) => { + context.service + .search(ac, { + query: '', + category: '', + offset: 0, + size: 6, + sortBy: section.sortBy, + sortOrder: 'desc' as SortOrder + }) + .then(result => { + if (!isError(result)) { + setSections(prev => + prev.map((s, i) => + i === idx ? { ...s, extensions: (result as any).extensions ?? [], loading: false } : s + ) + ); + } + }) + .catch(() => setSections(prev => prev.map((s, i) => (i === idx ? { ...s, loading: false } : s)))); + }); + + return () => ac.abort(); + }, []); + + const handleInputChange = (e: React.ChangeEvent) => { + const val = e.target.value; + setQuery(val); + clearTimeout(searchTimerRef.current); + if (val.trim()) { + searchTimerRef.current = window.setTimeout(() => onSearch(val.trim()), 250); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + clearTimeout(searchTimerRef.current); + if (query.trim()) onSearch(query.trim()); + }; + + return ( + + {/* ---- Hero ---- */} + + + + Open-source registry for VS Code–compatible editors + + + Find the right extension, +
+ for any editor. +
+ + Browse community-published extensions. Free, open, and vendor-neutral. + + + + + / + + + + + + + + + search + + + + + + + Popular: + + {POPULAR_CHIPS.map(chip => ( + onSearch(chip)} style={{ flexShrink: 0 }}> + {chip} + + ))} + +
+ + {/* ---- Categories ---- */} + {categories.length > 0 && ( + + + Browse by category + + {/* Mobile: horizontal scrollable pills */} + + {categories.map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return onSearch('', cat)} />; + })} + + {/* Desktop: grid */} + + {categories.map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return onSearch('', cat)} />; + })} + + + )} + + {/* ---- Curated rows ---- */} + {sections.map( + section => + !section.loading && + section.extensions.length > 0 && ( + + + + + {section.title} + + + {section.sub} + + + onSearch('')} + sx={{ + background: 'none', + border: 'none', + color: 'secondary.light', + fontSize: '14px', + fontWeight: 600, + cursor: 'pointer' + }}> + See all → + + + + {section.extensions.map((ext, idx) => ( + + ))} + + + ) + )} + + {/* ---- Get Involved ---- */} + + + Get Involved + + + {GET_INVOLVED_CARDS.map(card => ( + + + + + + {card.title} + + + {card.desc} + + + {card.label} + + + ))} + + +
+ ); +}; + +const GET_INVOLVED_CARDS = [ + { + icon: 'fork', + title: 'Contribute', + desc: 'Open VSX is fully open source. Help build the registry the ecosystem depends on.', + href: 'https://github.com/eclipse/openvsx', + label: 'View on GitHub →' + }, + { + icon: 'group', + title: 'Join the Working Group', + desc: 'Shape the future of an open, vendor-neutral marketplace for extensions.', + href: 'https://openvsxworkinggroup.github.io/', + label: 'Learn more →' + }, + { + icon: 'book', + title: 'Read the docs', + desc: 'Learn how to publish, claim namespaces, and consume extensions via the API.', + href: 'https://github.com/eclipse/openvsx/wiki', + label: 'Open documentation →' + } +]; + +const GetInvolvedIcon: FunctionComponent<{ icon: string }> = ({ icon }) => { + if (icon === 'fork') + return ( + + + + + + + ); + if (icon === 'group') + return ( + + + + + + + ); + return ( + + + + + ); +}; diff --git a/webui/src/pages/namespace-detail/namespace-detail.tsx b/webui/src/pages/namespace-detail/namespace-detail.tsx index 4e7e75ee1..151c2aec4 100644 --- a/webui/src/pages/namespace-detail/namespace-detail.tsx +++ b/webui/src/pages/namespace-detail/namespace-detail.tsx @@ -14,7 +14,7 @@ import GitHubIcon from '@mui/icons-material/GitHub'; import LinkedInIcon from '@mui/icons-material/LinkedIn'; import TwitterIcon from '@mui/icons-material/Twitter'; import { useParams } from 'react-router-dom'; -import { ExtensionListItem } from '../extension-list/extension-list-item'; +import { ExtensionCard } from '../../components/extension-card'; import { MainContext } from '../../context'; import { DelayedLoadIndicator } from '../../components/delayed-load-indicator'; import { NamespaceDetails, isError, UrlString } from '../../extension-registry-types'; @@ -235,7 +235,7 @@ export const NamespaceDetail: FunctionComponent = () => { {namespaceDetails.extensions.map((ext, idx) => ( - { - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(false); const context = useContext(MainContext); - const avatarButton = useRef(); + const anchorRef = useRef(null); const logoutFormRef = useRef(null); - const handleAvatarClick = () => { - setOpen(!open); - }; + const user = context.user; + if (!user) return null; - const handleClose = () => { - setOpen(false); - }; + const initials = user.loginName.slice(0, 2).toUpperCase(); - const user = context.user; - if (!user) { - return null; - } return ( <> (avatarButton.current = ref)}> + aria-label='User menu' + onClick={() => setOpen(true)} + sx={{ p: '5px' }}> + sx={{ + width: 32, + height: 32, + bgcolor: 'accentSoft', + color: 'secondary.light', + fontSize: '12px', + fontWeight: 700, + borderRadius: '8px' + }}> + {initials} + + onClose={() => setOpen(false)}> + {/* User header */} + + + {initials} + + + + Logged in as + + + {user.loginName} + + + + - - Logged in as - - - {user.loginName} - + target='_blank' + onClick={() => setOpen(false)} + sx={{ ...menuItemSx, textDecoration: 'none' }}> + + Your profile - - - - Settings - + setOpen(false)} + sx={{ ...menuItemSx, textDecoration: 'none' }}> + + Settings - {user.role && user.role === 'admin' ? ( - - - Admin Dashboard - + {user.role === 'admin' && ( + setOpen(false)} + sx={{ ...menuItemSx, textDecoration: 'none' }}> + + Admin Dashboard - ) : ( - '' )} - logoutFormRef.current?.submit()}> + + logoutFormRef.current?.submit()} sx={menuItemSx}> - - Log Out - + + + + Log out + + diff --git a/webui/src/use-shortcut.ts b/webui/src/use-shortcut.ts new file mode 100644 index 000000000..4fe41aaeb --- /dev/null +++ b/webui/src/use-shortcut.ts @@ -0,0 +1,35 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + ********************************************************************************/ + +import { useEffect, useRef } from 'react'; +import { useKeyboardShortcuts } from './keyboard-shortcuts-context'; + +interface UseShortcutOptions { + key: string; + label: string; + callback: () => void; + order?: number; + enabled?: boolean; +} + +/** + * Register a keyboard shortcut for the lifetime of the calling component. + * The shortcut is automatically unregistered when the component unmounts. + * Callback is kept fresh via a ref — no re-registration on re-render. + */ +export function useShortcut({ key, label, callback, order = 99, enabled = true }: UseShortcutOptions): void { + const { register, unregister } = useKeyboardShortcuts(); + + // Keep callback fresh without forcing re-registration on every render + const callbackRef = useRef(callback); + callbackRef.current = callback; + + useEffect(() => { + if (!enabled) return; + register({ key, label, order, callback: () => callbackRef.current() }); + return () => unregister(key); + }, [key, label, order, enabled]); +} From 9b33fa9f48c20fcd6e3f93ef6808d0a60744f4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Wed, 1 Jul 2026 22:55:31 +0200 Subject: [PATCH 02/81] chore: cleaning up redesign implementation --- webui/src/components/category-card.tsx | 11 +- webui/src/components/extension-card.tsx | 113 ++-- .../src/components/extension-searchfield.tsx | 2 +- webui/src/components/layout.tsx | 50 ++ webui/src/default/default-app.tsx | 14 +- webui/src/default/page-settings.tsx | 158 +++-- webui/src/default/theme.tsx | 2 +- webui/src/index.ts | 2 +- webui/src/keyboard-shortcuts-context.tsx | 4 +- webui/src/layout/app-footer.tsx | 290 ++++----- webui/src/layout/app-layout.tsx | 4 +- webui/src/layout/app-navbar.tsx | 58 +- webui/src/layout/nav-search-field.tsx | 2 +- webui/src/main.css | 17 +- webui/src/nav-search-context.tsx | 11 +- webui/src/page-settings.ts | 91 ++- .../pages/admin-dashboard/extension-admin.tsx | 4 +- .../extension-detail/extension-detail.tsx | 40 +- .../extension-rating-stars.tsx | 4 +- .../extension-list/extension-list-routes.ts | 2 +- webui/src/pages/home/home-page.tsx | 36 +- webui/src/pages/home/homepage-view.tsx | 548 +++++++----------- webui/src/pages/search/search-header.tsx | 114 ++++ webui/src/pages/search/search-page.tsx | 135 +++++ webui/src/pages/search/use-search-filter.ts | 132 +++++ webui/src/utils.ts | 10 + 26 files changed, 1069 insertions(+), 785 deletions(-) create mode 100644 webui/src/components/layout.tsx create mode 100644 webui/src/pages/search/search-header.tsx create mode 100644 webui/src/pages/search/search-page.tsx create mode 100644 webui/src/pages/search/use-search-filter.ts diff --git a/webui/src/components/category-card.tsx b/webui/src/components/category-card.tsx index 391faa88b..080e3dd61 100644 --- a/webui/src/components/category-card.tsx +++ b/webui/src/components/category-card.tsx @@ -7,26 +7,21 @@ import { FunctionComponent } from 'react'; import { Box, ButtonBase, SvgIconProps, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { cardHoverLift, cardSurface } from './layout'; const Root = styled(ButtonBase)(({ theme }) => ({ + ...cardSurface(theme), display: 'flex', flexDirection: 'column', alignItems: 'flex-start', justifyContent: 'flex-start', textAlign: 'left', overflow: 'hidden', - backgroundColor: theme.palette.background.paper, - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusCard, padding: '18px', color: theme.palette.text.primary, width: '100%', transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', - '&:hover': { - borderColor: theme.palette.secondary.main, - boxShadow: 'var(--shadow)', - transform: 'translateY(-2px)' - } + '&:hover': cardHoverLift(theme) })); export interface CategoryCardProps { diff --git a/webui/src/components/extension-card.tsx b/webui/src/components/extension-card.tsx index 0821439f1..b8988f63c 100644 --- a/webui/src/components/extension-card.tsx +++ b/webui/src/components/extension-card.tsx @@ -4,9 +4,8 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useContext, useState, useEffect, useRef } from 'react'; -import { flushSync } from 'react-dom'; -import { Link as RouteLink, useNavigate } from 'react-router-dom'; +import { memo, useContext, useEffect, useState } from 'react'; +import { Link as RouteLink } from 'react-router-dom'; import { Paper, Typography, Box, Fade } from '@mui/material'; import { styled, alpha } from '@mui/material/styles'; import SaveAltIcon from '@mui/icons-material/SaveAlt'; @@ -14,10 +13,12 @@ import { MainContext } from '../context'; import { ExtensionDetailRoutes } from '../pages/extension-detail/extension-detail-routes'; import { SearchEntry } from '../extension-registry-types'; import { ExtensionRatingStars } from '../pages/extension-detail/extension-rating-stars'; -import { createRoute } from '../utils'; +import { createRoute, formatCompactNumber } from '../utils'; import { MONO_FONT } from '../default/theme'; +import { cardHoverLift, cardSurface } from './layout'; const CardRoot = styled(Paper)(({ theme }) => ({ + ...cardSurface(theme), padding: '22px 16px', [theme.breakpoints.down('sm')]: { padding: '14px 10px' }, display: 'flex', @@ -26,76 +27,56 @@ const CardRoot = styled(Paper)(({ theme }) => ({ textAlign: 'center', height: '100%', minHeight: '206px', - border: `1px solid ${theme.palette.divider}`, - borderRadius: theme.shape.borderRadiusCard, - backgroundColor: theme.palette.background.paper, cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', - '&:hover': { - borderColor: theme.palette.secondary.main, - boxShadow: 'var(--shadow)', - transform: 'translateY(-2px)' - }, - // Keyboard focus ring — mirrors the search field's :focus-within style + '&:hover': cardHoverLift(theme), + // Keyboard focus ring mirrors the search field's :focus-within style. 'a:focus-visible &': { borderColor: theme.palette.secondary.main, boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}` } })); -export const ExtensionCard: FunctionComponent = props => { - const [icon, setIcon] = useState(); +export interface ExtensionCardProps { + extension: SearchEntry; + idx: number; + filterSize: number; +} + +export const ExtensionCard = memo(function ExtensionCard({ extension, idx, filterSize }: ExtensionCardProps) { const context = useContext(MainContext); - const abortController = useRef(new AbortController()); + const [icon, setIcon] = useState(); useEffect(() => { - updateChanges(); + const abortController = new AbortController(); + let objectUrl: string | undefined; + context.service + .getExtensionIcon(abortController, extension) + .then(url => { + objectUrl = url; + setIcon(url); + }) + .catch(err => { + if (!abortController.signal.aborted) context.handleError(err); + }); return () => { - abortController.current.abort(); - if (icon) URL.revokeObjectURL(icon); + abortController.abort(); + if (objectUrl) URL.revokeObjectURL(objectUrl); }; - }, []); + }, [extension.namespace, extension.name, extension.version]); - useEffect(() => { - updateChanges(); - }, [props.extension.namespace, props.extension.name, props.extension.version]); - - const updateChanges = async (): Promise => { - if (icon) URL.revokeObjectURL(icon); - try { - const icon = await context.service.getExtensionIcon(abortController.current, props.extension); - setIcon(icon); - } catch (err) { - context.handleError(err); - } - }; - - const { extension, filterSize, idx } = props; - const navigate = useNavigate(); const route = createRoute([ExtensionDetailRoutes.ROOT, extension.namespace, extension.name]); - const numberFormat = new Intl.NumberFormat(undefined, { notation: 'compact', compactDisplay: 'short' } as any); - const downloadCountFormatted = numberFormat.format(extension.downloadCount ?? 0); - const vtName = `ext-${extension.namespace}-${extension.name}`.replace(/[^a-zA-Z0-9-]/g, '-'); - - const handleCardClick = (e: React.MouseEvent) => { - if (!('startViewTransition' in document)) return; - e.preventDefault(); - const img = (e.currentTarget as HTMLElement).querySelector('img'); - if (img) img.style.viewTransitionName = vtName; - (document as any).startViewTransition(() => { - flushSync(() => navigate(route)); - }); - }; + const downloadCount = formatCompactNumber(extension.downloadCount ?? 0); + const title = extension.displayName ?? extension.name; return ( - - + + + aria-label={title} + style={{ textDecoration: 'none', height: '100%', display: 'block', outline: 'none' }}> @@ -105,15 +86,15 @@ export const ExtensionCard: FunctionComponent = props => { alignItems='center' flexShrink={0} sx={{ - width: { xs: 40, sm: 54 }, - height: { xs: 40, sm: 54 }, - mb: { xs: '10px', sm: '14px' } + width: 54, + height: 54, + mb: '14px' }}> = props => { WebkitBoxOrient: 'vertical', overflow: 'hidden' }}> - {extension.displayName ?? extension.name} + {title} = props => { - {downloadCountFormatted !== '0' && ( + {downloadCount !== '0' && ( = props => { color: 'text.disabled' }}> - {downloadCountFormatted} + {downloadCount} )} @@ -188,10 +169,4 @@ export const ExtensionCard: FunctionComponent = props => { ); -}; - -export interface ExtensionCardProps { - extension: SearchEntry; - idx: number; - filterSize: number; -} +}); diff --git a/webui/src/components/extension-searchfield.tsx b/webui/src/components/extension-searchfield.tsx index 8ceec0225..d356e690a 100644 --- a/webui/src/components/extension-searchfield.tsx +++ b/webui/src/components/extension-searchfield.tsx @@ -37,7 +37,7 @@ const SearchWrap = styled(Box, { border: hasError ? '2px solid' : '1px solid', borderColor: hasError ? theme.palette.error.main : theme.palette.divider, borderRadius: '11px', - height: '52px', + height: '45px', padding: '0 13px', backgroundColor: theme.palette.surface2, transition: 'border-color 0.18s, box-shadow 0.18s', diff --git a/webui/src/components/layout.tsx b/webui/src/components/layout.tsx new file mode 100644 index 000000000..60d0bf7fc --- /dev/null +++ b/webui/src/components/layout.tsx @@ -0,0 +1,50 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { Box, Typography } from '@mui/material'; +import { styled, Theme } from '@mui/material/styles'; + +/** Max width of the centered content column shared by every page section. */ +export const CONTENT_MAX_WIDTH = 1320; + +/** + * Horizontally-centered content column with responsive gutters. The single + * source of truth for page width so sections stay aligned across the app. + */ +export const Section = styled(Box)(({ theme }) => ({ + maxWidth: CONTENT_MAX_WIDTH, + marginInline: 'auto', + paddingInline: '28px', + [theme.breakpoints.down('sm')]: { + paddingInline: '16px' + } +})); + +/** Small uppercase label used to head sections, columns and sidebars. */ +export const Eyebrow = styled(Typography)(({ theme }) => ({ + fontSize: '12px', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: '0.08em', + color: theme.palette.text.disabled +})) as typeof Typography; + +/** + * Elevated surface shared by cards (extensions, categories, panels): paper + * background, hairline border and the card corner radius from the theme. + */ +export const cardSurface = (theme: Theme) => ({ + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadiusCard +}); + +/** Hover treatment for interactive cards: accent border, shadow and lift. */ +export const cardHoverLift = (theme: Theme) => ({ + borderColor: theme.palette.secondary.main, + boxShadow: 'var(--shadow)', + transform: 'translateY(-2px)' +}); diff --git a/webui/src/default/default-app.tsx b/webui/src/default/default-app.tsx index ad1135399..6db67b827 100644 --- a/webui/src/default/default-app.tsx +++ b/webui/src/default/default-app.tsx @@ -43,22 +43,10 @@ if (serverHost.startsWith('3000-')) { } const service = new ExtensionRegistryService(`${location.protocol}//${serverHost}`); -async function getServerVersion(): Promise { - const abortController = new AbortController(); - try { - const result = await service.getRegistryVersion(abortController); - return result.version; - } catch (error) { - console.error('Could not determine server version'); - return 'unknown'; - } -} - export const App = () => { const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); const theme = useMemo(() => createDefaultTheme(prefersDarkMode ? 'dark' : 'light'), [prefersDarkMode]); - - const pageSettings = createPageSettings(prefersDarkMode, service.serverUrl, getServerVersion()); + const pageSettings = useMemo(() => createPageSettings(prefersDarkMode, service.serverUrl), [prefersDarkMode]); return ( diff --git a/webui/src/default/page-settings.tsx b/webui/src/default/page-settings.tsx index b4b875422..da08f18d2 100644 --- a/webui/src/default/page-settings.tsx +++ b/webui/src/default/page-settings.tsx @@ -8,83 +8,116 @@ * SPDX-License-Identifier: EPL-2.0 ********************************************************************************/ -import { FunctionComponent, ReactNode, Suspense, lazy } from 'react'; +import { FunctionComponent, ReactNode } from 'react'; import { Helmet } from 'react-helmet-async'; -import { styled, Theme } from '@mui/material/styles'; -import { Link, Typography, Box } from '@mui/material'; +import { Typography, Box } from '@mui/material'; import { Link as RouteLink, Route, useParams } from 'react-router-dom'; import GitHubIcon from '@mui/icons-material/GitHub'; +import LinkedInIcon from '@mui/icons-material/LinkedIn'; +import XIcon from '@mui/icons-material/X'; +import CallSplitIcon from '@mui/icons-material/CallSplit'; +import GroupsIcon from '@mui/icons-material/Groups'; +import MenuBookIcon from '@mui/icons-material/MenuBook'; import { Extension, NamespaceDetails } from '../extension-registry-types'; import { PageSettings } from '../page-settings'; import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; import { DefaultMenuContent, MobileMenuContent } from './menu-content'; +import { OpenVsxMark } from '../components/openvsx-mark'; import OpenVSXLogo from './openvsx-registry-logo'; import About from './about'; import { createAbsoluteURL } from '../utils'; -export default function createPageSettings( - prefersDarkMode: boolean, - serverUrl: string, - serverVersionPromise: Promise -): PageSettings { +const WIKI_URL = 'https://github.com/eclipse/openvsx/wiki'; +const REPO_URL = 'https://github.com/eclipse/openvsx'; +const SLACK_URL = 'https://join.slack.com/t/openvsxworkinggroup/shared_invite/zt-2y07y1ggy-ct3IfJljjGI6xWUQ9llv6A'; + +export default function createPageSettings(prefersDarkMode: boolean, serverUrl: string): PageSettings { const toolbarContent: FunctionComponent = () => ( ); - const link = ({ theme }: { theme: Theme }) => ({ - color: theme.palette.text.primary, - textDecoration: 'none', - '&:hover': { - color: theme.palette.secondary.main, - textDecoration: 'none' - } - }); - - const StyledRouteLink = styled(RouteLink)(link); - - const ServerVersion = lazy(async () => { - const version = await serverVersionPromise; - return { - default: () => ( - - Server Version: {version} - - ) - }; - }); + const footer: PageSettings['elements']['footer'] = { + brand: { + logo: , + name: 'Open VSX Registry', + description: 'An open-source, vendor-neutral registry for VS Code–compatible extensions.' + }, + columns: [ + { + heading: 'Resources', + links: [ + { label: 'Documentation', href: WIKI_URL }, + { label: 'API Reference', href: '/swagger-ui.html' }, + { label: 'Publishing Guide', href: `${WIKI_URL}/Publishing-Extensions` }, + { label: 'Status', href: 'https://status.eclipse.org/' }, + { label: 'Commercial Usage', href: 'https://www.eclipse.org/legal/open-vsx-registry.php' } + ] + }, + { + heading: 'Community', + links: [ + { label: 'GitHub', href: REPO_URL, external: true }, + { label: 'Working Group', href: 'https://openvsxworkinggroup.github.io/', external: true }, + { label: 'Report a Vulnerability', href: `${REPO_URL}/security`, external: true }, + { label: 'Slack Workspace', href: SLACK_URL, external: true } + ] + }, + { + heading: 'Legal', + links: [ + { label: 'Privacy Policy', href: 'https://www.eclipse.org/legal/privacy.php', external: true }, + { label: 'Terms of Use', href: 'https://www.eclipse.org/legal/termsofuse.php', external: true }, + { label: 'Security Policy', href: `${REPO_URL}/security/policy`, external: true }, + { label: 'Eclipse Foundation', href: 'https://www.eclipse.org', external: true } + ] + } + ], + social: [ + { title: 'GitHub', href: REPO_URL, icon: }, + { + title: 'LinkedIn', + href: 'https://www.linkedin.com/company/eclipse-foundation/', + icon: + }, + { title: 'X (Twitter)', href: 'https://twitter.com/EclipseFdn', icon: } + ], + copyright: 'Copyright © Eclipse Foundation, AISBL. All Rights Reserved.' + }; - const footerContent: FunctionComponent<{ expanded: boolean }> = () => ( - - ({ - ...link({ theme }), - display: 'flex', - alignItems: 'center', - fontSize: '1.1rem', - mb: { xs: 1, sm: 1, md: 0, lg: 0, xl: 0 } - })}> - -  eclipse/openvsx - - Loading version...}> - - - About This Service - - ); + const home: PageSettings['elements']['home'] = { + popularSearches: ['python', 'git', 'docker', 'prettier', 'eslint', 'rust', 'java'], + involvement: { + heading: 'Get Involved', + cards: [ + { + icon: , + title: 'Contribute', + description: 'Open VSX is fully open source. Help build the registry the ecosystem depends on.', + href: REPO_URL, + label: 'View on GitHub →' + }, + { + icon: , + title: 'Join the Working Group', + description: 'Shape the future of an open, vendor-neutral marketplace for extensions.', + href: 'https://openvsxworkinggroup.github.io/', + label: 'Learn more →' + }, + { + icon: , + title: 'Read the docs', + description: 'Learn how to publish, claim namespaces, and consume extensions via the API.', + href: WIKI_URL, + label: 'Open documentation →' + } + ] + } + }; const searchHeader: FunctionComponent = () => ( - + - Browse community-published extensions. Free, open, and vendor-neutral. + Browse community-published extensions.
+ Free, open, and vendor-neutral.
); @@ -175,12 +209,8 @@ export default function createPageSettings( toolbarContent, defaultMenuContent: DefaultMenuContent, mobileMenuContent: MobileMenuContent, - footer: { - content: footerContent, - props: { - footerHeight: 69 // Maximal height reached for small screens - } - }, + footer, + home, searchHeader, additionalRoutes, mainHeadTags, diff --git a/webui/src/default/theme.tsx b/webui/src/default/theme.tsx index 83223f4c3..0ef68015d 100644 --- a/webui/src/default/theme.tsx +++ b/webui/src/default/theme.tsx @@ -81,7 +81,7 @@ declare module '@mui/material/styles/createPalette' { } } -declare module '@mui/material/styles' { +declare module '@mui/system/createTheme/shape' { interface Shape { borderRadiusCard: number; } diff --git a/webui/src/index.ts b/webui/src/index.ts index 1b754e9e3..2317954a9 100644 --- a/webui/src/index.ts +++ b/webui/src/index.ts @@ -13,6 +13,6 @@ export * from './page-settings'; export * from './extension-registry-service'; export * from './extension-registry-types'; export * from './pages/extension-detail/extension-detail'; -export * from './pages/extension-list/extension-list'; +export * from './components/extension-list'; export * from './pages/namespace-detail/namespace-detail'; export * from './pages/user/user-settings'; diff --git a/webui/src/keyboard-shortcuts-context.tsx b/webui/src/keyboard-shortcuts-context.tsx index 60de90f9b..a82628943 100644 --- a/webui/src/keyboard-shortcuts-context.tsx +++ b/webui/src/keyboard-shortcuts-context.tsx @@ -11,6 +11,7 @@ import { useCallback, useContext, useEffect, + useMemo, useRef, useState } from 'react'; @@ -131,7 +132,8 @@ export const KeyboardShortcutsProvider: FunctionComponent = ( }, []); return ( - + ({ shortcuts, register, unregister }), [shortcuts, register, unregister])}> {children} ); diff --git a/webui/src/layout/app-footer.tsx b/webui/src/layout/app-footer.tsx index d22b2b48f..1cde65ac0 100644 --- a/webui/src/layout/app-footer.tsx +++ b/webui/src/layout/app-footer.tsx @@ -8,8 +8,9 @@ import { FunctionComponent, useContext, useEffect, useState } from 'react'; import { Box, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { KbdKey } from '../components/kbd-key'; -import { OpenVsxMark } from '../components/openvsx-mark'; +import { Section, Eyebrow } from '../components/layout'; import { MONO_FONT } from '../default/theme'; +import { CustomFooterSettings, StructuredFooterSettings } from '../page-settings'; import { MainContext } from '../context'; const FooterLink = styled('a')(({ theme }) => ({ @@ -20,15 +21,6 @@ const FooterLink = styled('a')(({ theme }) => ({ '&:hover': { color: theme.palette.secondary.light } })); -const FooterColumnHead = styled(Typography)(({ theme }) => ({ - fontSize: '12px', - fontWeight: 700, - textTransform: 'uppercase', - letterSpacing: '0.08em', - color: theme.palette.text.disabled, - marginBottom: '14px' -})); - const SocialIconButton = styled('a')(({ theme }) => ({ width: 34, height: 34, @@ -47,195 +39,153 @@ const SocialIconButton = styled('a')(({ theme }) => ({ } })); -const GitHubSvg = () => ( - - - -); - -const LinkedInSvg = () => ( - - - -); +const ShortcutsButton = styled('button')(({ theme }) => ({ + background: 'none', + border: 'none', + cursor: 'pointer', + padding: 0, + display: 'flex', + alignItems: 'center', + gap: '8px', + fontSize: '12.5px', + color: theme.palette.text.disabled, + '&:hover': { color: theme.palette.secondary.light } +})); -const XSvg = () => ( - - - -); +/** Legacy footer chrome: renders a consumer-provided component, hover toggles `expanded`. */ +const LegacyFooterRoot = styled('footer')(({ theme }) => ({ + marginTop: 'auto', + width: '100%', + padding: `${theme.spacing(1)} ${theme.spacing(2)}`, + backgroundColor: theme.palette.background.paper, + boxShadow: '0px -2px 6px 0px rgba(0, 0, 0, 0.5)', + backgroundImage: + theme.palette.mode === 'dark' + ? 'linear-gradient(rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.08))' + : undefined +})); -const FOOTER_LINKS = { - resources: [ - { label: 'Documentation', href: 'https://github.com/eclipse/openvsx/wiki' }, - { label: 'API Reference', href: '/swagger-ui.html' }, - { label: 'Publishing Guide', href: 'https://github.com/eclipse/openvsx/wiki/Publishing-Extensions' }, - { label: 'Status', href: 'https://status.eclipse.org/' }, - { label: 'Commercial Usage', href: 'https://www.eclipse.org/legal/open-vsx-registry.php' } - ], - community: [ - { label: 'GitHub', href: 'https://github.com/eclipse/openvsx', external: true }, - { label: 'Working Group', href: 'https://openvsxworkinggroup.github.io/', external: true }, - { label: 'Report a Vulnerability', href: 'https://github.com/eclipse/openvsx/security', external: true }, - { - label: 'Slack Workspace', - href: 'https://join.slack.com/t/openvsxworkinggroup/shared_invite/zt-2y07y1ggy-ct3IfJljjGI6xWUQ9llv6A', - external: true - } - ], - legal: [ - { label: 'Privacy Policy', href: 'https://www.eclipse.org/legal/privacy.php', external: true }, - { label: 'Terms of Use', href: 'https://www.eclipse.org/legal/termsofuse.php', external: true }, - { label: 'Security Policy', href: 'https://github.com/eclipse/openvsx/security/policy', external: true }, - { label: 'Eclipse Foundation', href: 'https://www.eclipse.org', external: true } - ] +const LegacyFooter: FunctionComponent<{ footer: CustomFooterSettings }> = ({ footer }) => { + const Content = footer.content; + const [expanded, setExpanded] = useState(false); + return ( + setExpanded(true)} onMouseLeave={() => setExpanded(false)}> + + + ); }; -const SOCIAL_LINKS = [ - { href: 'https://github.com/eclipse/openvsx', icon: , title: 'GitHub' }, - { href: 'https://www.linkedin.com/company/eclipse-foundation/', icon: , title: 'LinkedIn' }, - { href: 'https://twitter.com/EclipseFdn', icon: , title: 'X (Twitter)' } -]; - export interface AppFooterProps { onOpenShortcuts: () => void; } export const AppFooter: FunctionComponent = ({ onOpenShortcuts }) => { - const { service } = useContext(MainContext); + const { service, pageSettings } = useContext(MainContext); + const footer = pageSettings.elements.footer; const [version, setVersion] = useState(null); useEffect(() => { - const ac = new AbortController(); + const abortController = new AbortController(); service - .getRegistryVersion(ac) + .getRegistryVersion(abortController) .then(r => setVersion(r.version)) .catch(() => {}); - return () => ac.abort(); - }, []); + return () => abortController.abort(); + }, [service]); - return ( - - {/* Columns */} - ; + } + + return ; +}; + +interface StructuredFooterProps { + footer?: StructuredFooterSettings; + version: string | null; + onOpenShortcuts: () => void; +} + +const StructuredFooter: FunctionComponent = ({ footer, version, onOpenShortcuts }) => ( + + {footer && (footer.brand || footer.columns) && ( +
- {/* Brand */} - - - - Open VSX Registry - - - An open-source, vendor-neutral registry for VS Code–compatible extensions. - - - {SOCIAL_LINKS.map(s => ( - - {s.icon} - - ))} - - - {/* Resources */} - - Resources - - {FOOTER_LINKS.resources.map(l => ( - - {l.label} - - ))} - - - {/* Community */} - - Community - - {FOOTER_LINKS.community.map(l => ( - - {l.label} - - ))} - - Keyboard shortcuts - ? + {footer.brand && ( + + + {footer.brand.logo} + {footer.brand.name} + {footer.brand.description && ( + + {footer.brand.description} + + )} + {footer.social && footer.social.length > 0 && ( + + {footer.social.map(s => ( + + {s.icon} + + ))} + + )} - - {/* Legal */} - - Legal - - {FOOTER_LINKS.legal.map(l => ( - - {l.label} - - ))} + )} + {footer.columns?.map((column, ci) => ( + + {column.heading} + + {column.links.map((l, li) => ( + + {l.label} + + ))} + - - - {/* Bottom bar */} - - - Copyright © Eclipse Foundation, AISBL. All Rights Reserved. - + ))} +
+ )} +
+ {footer?.copyright} + + {footer?.extra} + + Keyboard shortcuts + ? + {version && ( v{version} )} - - ); -}; +
+
+); diff --git a/webui/src/layout/app-layout.tsx b/webui/src/layout/app-layout.tsx index 1841ab921..0e38d5e40 100644 --- a/webui/src/layout/app-layout.tsx +++ b/webui/src/layout/app-layout.tsx @@ -22,7 +22,7 @@ import { NamespaceDetailRoutes } from '../pages/namespace-detail/namespace-detai import { ExtensionDetailRoutes } from '../pages/extension-detail/extension-detail-routes'; import { ExtensionDetail } from '../pages/extension-detail/extension-detail'; import { HomePage } from '../pages/home/home-page'; -import { BrowsePage } from '../pages/browse/browse-page'; +import { SearchPage } from '../pages/search/search-page'; import { NamespaceDetail } from '../pages/namespace-detail/namespace-detail'; import { NotFound } from '../not-found'; import { AppNavbar } from './app-navbar'; @@ -98,7 +98,7 @@ const AppLayoutContent: FunctionComponent = props => { } /> - } /> + } /> } diff --git a/webui/src/layout/app-navbar.tsx b/webui/src/layout/app-navbar.tsx index e1dfec859..4320ac1db 100644 --- a/webui/src/layout/app-navbar.tsx +++ b/webui/src/layout/app-navbar.tsx @@ -4,11 +4,10 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useCallback, useContext, useEffect, useState } from 'react'; -import { flushSync } from 'react-dom'; +import { FunctionComponent, useContext, useEffect, useState } from 'react'; import { AppBar, Box, Toolbar } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; -import { Link as RouteLink, useNavigate } from 'react-router-dom'; +import { Link as RouteLink } from 'react-router-dom'; import { HeaderMenu } from '../header-menu'; import { MainContext } from '../context'; import { useNavSearch } from '../nav-search-context'; @@ -20,15 +19,18 @@ const ToolbarItem = styled(Box)({ alignItems: 'center' }); +// Progressive ("gradient") blur: blur doubles each layer and every mask is an +// equal-width window that overlaps its neighbours by a constant 12.5% step, so +// the layers blend into one continuous ramp instead of visible bands. const BLUR_LAYERS = [ - { blur: '22px', sat: 2.0, stops: 'transparent 87.5%, #000 100%' }, - { blur: '14px', sat: 1.7, stops: 'transparent 75%, #000 87.5%, #000 100%' }, - { blur: '8px', sat: 1.45, stops: 'transparent 62.5%, #000 75%, #000 87.5%, transparent 100%' }, - { blur: '5px', sat: 1.4, stops: 'transparent 50%, #000 62.5%, #000 75%, transparent 87.5%' }, - { blur: '3px', sat: 1.35, stops: 'transparent 37.5%, #000 50%, #000 62.5%, transparent 75%' }, - { blur: '2px', sat: 1.3, stops: 'transparent 25%, #000 37.5%, #000 50%, transparent 62.5%' }, - { blur: '1px', sat: 1, stops: 'transparent 12.5%, #000 25%, #000 37.5%, transparent 50%' }, - { blur: '0.5px', sat: 1, stops: '#000 0%, #000 12.5%, #000 25%, transparent 37.5%' } + { blur: '64px', sat: 1.3, stops: 'transparent 75%, #000 87.5%, #000 100%' }, + { blur: '32px', sat: 1.3, stops: 'transparent 62.5%, #000 75%, #000 87.5%, transparent 100%' }, + { blur: '16px', sat: 1.3, stops: 'transparent 50%, #000 62.5%, #000 75%, transparent 87.5%' }, + { blur: '8px', sat: 1.3, stops: 'transparent 37.5%, #000 50%, #000 62.5%, transparent 75%' }, + { blur: '4px', sat: 1.3, stops: 'transparent 25%, #000 37.5%, #000 50%, transparent 62.5%' }, + { blur: '2px', sat: 1, stops: 'transparent 12.5%, #000 25%, #000 37.5%, transparent 50%' }, + { blur: '1px', sat: 1, stops: 'transparent 0%, #000 12.5%, #000 25%, transparent 37.5%' }, + { blur: '0.5px', sat: 1, stops: '#000 0%, #000 12.5%, transparent 25%' } ]; export const AppNavbar: FunctionComponent = () => { @@ -36,24 +38,9 @@ export const AppNavbar: FunctionComponent = () => { const { toolbarContent: ToolbarContent } = pageSettings.elements; const { isHeroPage } = useNavSearch(); const theme = useTheme(); - const navigate = useNavigate(); const navbg = theme.palette.mode === 'dark' ? 'rgba(14, 14, 20, 0.74)' : 'rgba(255, 255, 255, 0.78)'; const [scrolled, setScrolled] = useState(false); - // Trigger the reverse view-transition (nav search → hero) when navigating home - const handleHomeClick = useCallback( - (e: { preventDefault(): void; stopPropagation(): void }) => { - if (isHeroPage) return; - if (!('startViewTransition' in document)) return; - e.preventDefault(); - e.stopPropagation(); - (document as any).startViewTransition(() => { - flushSync(() => navigate('/')); - }); - }, - [isHeroPage, navigate] - ); - useEffect(() => { const onScroll = () => setScrolled(window.scrollY > 20); window.addEventListener('scroll', onScroll, { passive: true }); @@ -92,7 +79,7 @@ export const AppNavbar: FunctionComponent = () => { left: 0, right: 0, top: 0, - bottom: '-100px', + bottom: { xs: '-48px', sm: '-100px' }, pointerEvents: 'none', zIndex: 0, opacity: showFan ? 1 : 0, @@ -111,7 +98,7 @@ export const AppNavbar: FunctionComponent = () => { left: 0, right: 0, top: 0, - bottom: '-100px', + bottom: { xs: '-48px', sm: '-100px' }, pointerEvents: 'none', zIndex: 0, opacity: showFan ? 1 : 0, @@ -123,26 +110,19 @@ export const AppNavbar: FunctionComponent = () => { sx={{ justifyContent: 'space-between', minHeight: '62px !important', - px: '28px !important', + px: { xs: '10px !important', sm: '28px !important' }, position: 'relative', zIndex: 1 }}> {/* Mobile compact icon — shown on non-home pages */} - + - {/* Full logo — desktop always, mobile only on home page. - onClickCapture intercepts the inner RouteLink so we can wrap it in startViewTransition. */} - + {/* Full logo — desktop always, mobile only on home page. */} + {ToolbarContent ? : null} diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx index 356e0ad8b..1c908f207 100644 --- a/webui/src/layout/nav-search-field.tsx +++ b/webui/src/layout/nav-search-field.tsx @@ -43,7 +43,7 @@ export const NavSearchField: FunctionComponent = () => { if (searchHandlerRef.current) { searchHandlerRef.current(q); } else { - navigate(`/browse${q ? '?q=' + encodeURIComponent(q) : ''}`); + navigate(`/search${q ? '?q=' + encodeURIComponent(q) : ''}`); } }, [navigate, setNavQuery] diff --git a/webui/src/main.css b/webui/src/main.css index 512f631cd..7ccf7c1e9 100644 --- a/webui/src/main.css +++ b/webui/src/main.css @@ -51,11 +51,15 @@ img { } /* View transition for search bar morph */ +::view-transition-group(vt-search) { + animation-duration: 120ms; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} ::view-transition-old(vt-search) { - animation: 160ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-out; + animation: 120ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-out; } ::view-transition-new(vt-search) { - animation: 160ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-in; + animation: 120ms cubic-bezier(0.4, 0, 0.2, 1) both vt-search-in; } @keyframes vt-search-out { to { @@ -70,17 +74,10 @@ img { } } -/* View transition for extension icon card → detail morph */ -::view-transition-old(ext-icon), -::view-transition-new(ext-icon) { - animation-duration: 220ms; - animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - /* Root crossfade when navigating between pages */ ::view-transition-old(root), ::view-transition-new(root) { - animation-duration: 160ms; + animation-duration: 120ms; } @keyframes fadeout { diff --git a/webui/src/nav-search-context.tsx b/webui/src/nav-search-context.tsx index 1b3495233..8ed689d34 100644 --- a/webui/src/nav-search-context.tsx +++ b/webui/src/nav-search-context.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { createContext, FunctionComponent, ReactNode, useCallback, useContext, useState } from 'react'; +import { createContext, FunctionComponent, ReactNode, useCallback, useContext, useMemo, useState } from 'react'; interface NavSearchContextValue { isHeroPage: boolean; @@ -32,12 +32,11 @@ export const NavSearchProvider: FunctionComponent<{ children: ReactNode }> = ({ const setSearchHandler = useCallback((fn: ((q: string) => void) | null) => { setSearchHandlerState(() => fn); }, []); - return ( - - {children} - + const value = useMemo( + () => ({ isHeroPage, setIsHeroPage, navQuery, setNavQuery, searchHandler, setSearchHandler }), + [isHeroPage, navQuery, searchHandler, setSearchHandler] ); + return {children}; }; // eslint-disable-next-line react-refresh/only-export-components diff --git a/webui/src/page-settings.ts b/webui/src/page-settings.ts index 6108f42fc..6adc7f1ee 100644 --- a/webui/src/page-settings.ts +++ b/webui/src/page-settings.ts @@ -10,9 +10,90 @@ import { ComponentType, ReactNode } from 'react'; import { SxProps, Theme } from '@mui/material/styles'; -import { Extension, NamespaceDetails } from './extension-registry-types'; +import { Extension, NamespaceDetails, SortBy } from './extension-registry-types'; import { Cookie } from './utils'; +export interface FooterLink { + label: ReactNode; + href: string; + /** Open in a new tab. */ + external?: boolean; +} + +export interface FooterColumn { + heading: ReactNode; + links: FooterLink[]; +} + +export interface FooterSocialLink { + /** Accessible label / tooltip for the icon button. */ + title: string; + href: string; + icon: ReactNode; +} + +/** + * Structured, data-driven footer rendered by the library chrome (brand, link + * columns and social icons). Every field is consumer-provided branding and may + * be a plain string or any React node. + */ +export interface StructuredFooterSettings { + brand?: { + logo?: ReactNode; + name: ReactNode; + description?: ReactNode; + }; + columns?: FooterColumn[]; + social?: FooterSocialLink[]; + copyright?: ReactNode; + /** Extra node appended to the footer's bottom bar, next to the built-in controls. */ + extra?: ReactNode; +} + +/** + * Legacy footer: a fully custom component the layout renders at the bottom of + * the page, receiving an `expanded` flag that is true while hovered. Kept for + * backward compatibility with consumers written before {@link StructuredFooterSettings}. + */ +export interface CustomFooterSettings { + content: ComponentType<{ expanded: boolean }>; + props?: { + footerHeight?: number; + }; +} + +/** + * Footer configuration. Provide {@link CustomFooterSettings} (with `content`) to + * render your own footer component, or {@link StructuredFooterSettings} to feed + * the built-in footer chrome. The two are told apart by the presence of `content`. + */ +export type FooterSettings = CustomFooterSettings | StructuredFooterSettings; + +/** A curated row of extensions on the home page, fetched with the given ordering. */ +export interface HomeCuratedSection { + title: string; + subtitle: string; + sortBy: SortBy; +} + +export interface HomeInvolvementCard { + icon: ReactNode; + title: string; + description: string; + href: string; + label: string; +} + +/** Consumer-provided content for the home page (branding and curated data). */ +export interface HomeSettings { + popularSearches?: string[]; + curatedSections?: HomeCuratedSection[]; + involvement?: { + heading?: string; + cards: HomeInvolvementCard[]; + }; +} + export interface PageSettings { pageTitle: string; themeType?: 'light' | 'dark'; @@ -24,12 +105,8 @@ export interface PageSettings { toolbarContent?: ComponentType; defaultMenuContent?: ComponentType; mobileMenuContent?: ComponentType; - footer?: { - content: ComponentType<{ expanded: boolean }>; - props: { - footerHeight?: number; - }; - }; + footer?: FooterSettings; + home?: HomeSettings; searchHeader?: ComponentType; reportAbuse?: ComponentType<{ extension: Extension; sx?: SxProps }>; claimNamespace?: ComponentType<{ extension: Extension; sx?: SxProps }>; diff --git a/webui/src/pages/admin-dashboard/extension-admin.tsx b/webui/src/pages/admin-dashboard/extension-admin.tsx index d497562e2..ed6b9f18a 100644 --- a/webui/src/pages/admin-dashboard/extension-admin.tsx +++ b/webui/src/pages/admin-dashboard/extension-admin.tsx @@ -10,7 +10,7 @@ import { FunctionComponent, useState, useContext, useEffect } from 'react'; import { SearchListContainer } from './search-list-container'; -import { ExtensionListSearchfield } from '../extension-list/extension-list-searchfield'; +import { ExtensionSearchfield } from '../../components/extension-searchfield'; import { Button, Typography } from '@mui/material'; import { MainContext } from '../../context'; import { TargetPlatformVersion } from '../../extension-registry-types'; @@ -105,7 +105,7 @@ export const ExtensionAdmin: FunctionComponent = () => { hideIconButton={true} autoFocus={true} />, - {extension.license || 'Unlicensed'}; }; -const compactNumber = new Intl.NumberFormat(undefined, { - notation: 'compact', - compactDisplay: 'short' -} as Intl.NumberFormatOptions); - const ExtensionHeaderInfo: FunctionComponent<{ extension: Extension; headerTextColor: string; }> = ({ extension, headerTextColor }) => { - const downloadCountFormatted = compactNumber.format(extension.downloadCount || 0); - const reviewCountFormatted = compactNumber.format(extension.reviewCount || 0); + const downloadCountFormatted = formatCompactNumber(extension.downloadCount || 0); + const reviewCountFormatted = formatCompactNumber(extension.reviewCount || 0); return ( @@ -345,12 +340,6 @@ const ExtensionHeader: FunctionComponent<{ component='img' src={icon ?? pageSettings.urls.extensionDefaultIcon} alt={extension.displayName ?? extension.name} - style={{ - viewTransitionName: `ext-${extension.namespace}-${extension.name}`.replace( - /[^a-zA-Z0-9-]/g, - '-' - ) - }} sx={{ width: '96px', height: '96px', flexShrink: 0, objectFit: 'contain' }} /> { [navigate, namespace, name, target] ); - const _basePath = namespace && name ? buildExtensionPath(namespace, name, target) : ''; - const _reviewsPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.REVIEWS) : ''; - const _changesPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.CHANGES) : ''; + // Computed before the early return so the shortcut hooks below run unconditionally. + const basePath = namespace && name ? buildExtensionPath(namespace, name, target) : ''; + const changesPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.CHANGES) : ''; + const reviewsPath = namespace && name ? buildExtensionPath(namespace, name, target, ExtensionTab.REVIEWS) : ''; useShortcut({ key: 'o', label: 'Extension overview', order: 40, - callback: () => navigate(_basePath), - enabled: !!_basePath + callback: () => navigate(basePath), + enabled: !!basePath }); useShortcut({ key: 'c', label: 'Extension changelog', order: 50, - callback: () => navigate(_changesPath), - enabled: !!_basePath + callback: () => navigate(changesPath), + enabled: !!basePath }); useShortcut({ key: 'r', label: 'Extension reviews', order: 60, - callback: () => navigate(_reviewsPath), - enabled: !!_basePath + callback: () => navigate(reviewsPath), + enabled: !!basePath }); if (!namespace || !name) return null; - const basePath = buildExtensionPath(namespace, name, target); - const reviewsPath = buildExtensionPath(namespace, name, target, ExtensionTab.REVIEWS); - const changesPath = buildExtensionPath(namespace, name, target, ExtensionTab.CHANGES); - let overviewPath = basePath; if (version && !isTabSegment(version)) { overviewPath = buildExtensionPath(namespace, name, target, version); diff --git a/webui/src/pages/extension-detail/extension-rating-stars.tsx b/webui/src/pages/extension-detail/extension-rating-stars.tsx index 1e2b13ffb..77ae8e56e 100644 --- a/webui/src/pages/extension-detail/extension-rating-stars.tsx +++ b/webui/src/pages/extension-detail/extension-rating-stars.tsx @@ -23,11 +23,11 @@ export const ExtensionRatingStars: FunctionComponent const starsNumber = props.number; const fontSize = props.fontSize ?? 'medium'; if (i <= starsNumber) { - return ; + return ; } if (i > starsNumber && i - 1 < starsNumber) { return ( - + diff --git a/webui/src/pages/extension-list/extension-list-routes.ts b/webui/src/pages/extension-list/extension-list-routes.ts index 6032a091f..ed80c4674 100644 --- a/webui/src/pages/extension-list/extension-list-routes.ts +++ b/webui/src/pages/extension-list/extension-list-routes.ts @@ -15,5 +15,5 @@ import { createRoute } from '../../utils'; export namespace ExtensionListRoutes { export const MAIN = createRoute([]); - export const BROWSE = createRoute(['browse']); + export const SEARCH = createRoute(['search']); } diff --git a/webui/src/pages/home/home-page.tsx b/webui/src/pages/home/home-page.tsx index 449d1805f..638370ab3 100644 --- a/webui/src/pages/home/home-page.tsx +++ b/webui/src/pages/home/home-page.tsx @@ -23,10 +23,9 @@ export const HomePage: FunctionComponent = () => { const focusNavSearch = useCallback(() => { const input = document.getElementById('search-input') as HTMLInputElement | null; - if (input) { - input.focus(); - input.select(); - } + // preventScroll: a plain focus() scrolls the input into view, which on mobile + // fights the scroll-to-top and can leave the page sitting over the results. + input?.focus({ preventScroll: true }); }, []); const handleSearch = useCallback( @@ -35,21 +34,30 @@ export const HomePage: FunctionComponent = () => { const queries: { key: string; value: string }[] = []; if (query) queries.push({ key: 'q', value: query }); if (cat) queries.push({ key: 'category', value: cat }); - const url = addQuery(ExtensionListRoutes.BROWSE, queries); + const url = addQuery(ExtensionListRoutes.SEARCH, queries); + + const run = () => { + setNavQuery(query); + setIsHeroPage(false); + navigate(url, { state: { _q: query, _cat: cat } }); + }; + + // Only steal focus into the search bar for an actual text search — not when + // navigating to /search by browsing a category or "view all". + const shouldFocus = Boolean(query); if ('startViewTransition' in document) { const transition = (document as any).startViewTransition(() => { - flushSync(() => { - setNavQuery(query); - setIsHeroPage(false); - navigate(url, { state: { _q: query, _cat: cat } }); - }); + flushSync(run); + // Move focus to the nav search field synchronously, while the hero + // input is still focused, so the mobile keyboard stays open. Waiting + // for transition.finished leaves a gap that drops the keyboard. + if (shouldFocus) focusNavSearch(); }); - transition.finished.then(focusNavSearch); + if (shouldFocus) transition.finished.then(focusNavSearch); } else { - setNavQuery(query); - navigate(url, { state: { _q: query, _cat: cat } }); - requestAnimationFrame(() => requestAnimationFrame(focusNavSearch)); + flushSync(run); + if (shouldFocus) focusNavSearch(); } }, [navigate, setNavQuery, setIsHeroPage, focusNavSearch] diff --git a/webui/src/pages/home/homepage-view.tsx b/webui/src/pages/home/homepage-view.tsx index e2512f027..bb3c278f0 100644 --- a/webui/src/pages/home/homepage-view.tsx +++ b/webui/src/pages/home/homepage-view.tsx @@ -4,22 +4,28 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; +import { ComponentType, FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; import { Box, ButtonBase, Typography } from '@mui/material'; import { styled, alpha } from '@mui/material/styles'; import { MainContext } from '../../context'; import { useNavSearch } from '../../nav-search-context'; import { ExtensionCard } from '../../components/extension-card'; -import { SearchEntry, isError } from '../../extension-registry-types'; -import { SortBy, SortOrder } from '../../extension-registry-types'; +import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; +import { HomeCuratedSection } from '../../page-settings'; import { MONO_FONT } from '../../default/theme'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; import { CategoryCard } from '../../components/category-card'; +import { Section, Eyebrow } from '../../components/layout'; -const POPULAR_CHIPS = ['python', 'git', 'docker', 'prettier', 'eslint', 'rust', 'java']; +const CURATED_SIZE = 6; +const EXCLUDED_CATEGORIES = new Set(['Other', 'SCM Providers', 'Extension Packs']); -// ---- Styled components ---- +/** Curated rows shown when the consumer does not configure `home.curatedSections`. */ +const DEFAULT_CURATED_SECTIONS: HomeCuratedSection[] = [ + { title: 'Most downloaded', subtitle: 'The extensions developers rely on every day', sortBy: 'downloadCount' }, + { title: 'Recently updated', subtitle: 'Fresh releases from publishers this week', sortBy: 'timestamp' } +]; const HeroSearchWrap = styled(Box)(({ theme }) => ({ display: 'flex', @@ -104,12 +110,7 @@ const GetInvolvedCard = styled(Box)(({ theme }) => ({ flexDirection: 'column' })); -// ---- Types ---- - -interface CuratedSection { - title: string; - sub: string; - sortBy: SortBy; +interface CuratedRow extends HomeCuratedSection { extensions: SearchEntry[]; loading: boolean; } @@ -118,65 +119,21 @@ interface HomepageViewProps { onSearch: (query: string, category?: string) => void; } -// ---- Component ---- +interface HeroSearchProps { + onSearch: HomepageViewProps['onSearch']; + searchHeader?: ComponentType; + popularSearches: string[]; +} -export const HomepageView: FunctionComponent = ({ onSearch }) => { - const context = useContext(MainContext); +/** + * Owns the hero search field's `query` state so keystrokes only re-render the + * field, not the whole homepage (categories grid and curated rows). + */ +const HeroSearch: FunctionComponent = ({ onSearch, searchHeader: SearchHeader, popularSearches }) => { const { navQuery } = useNavSearch(); const [query, setQuery] = useState(() => navQuery); - const [categories, setCategories] = useState([]); - const [sections, setSections] = useState([ - { - title: 'Most Downloaded', - sub: 'The extensions developers rely on every day', - sortBy: 'downloadCount', - extensions: [], - loading: true - }, - { - title: 'Recently Updated', - sub: 'Fresh releases from publishers this week', - sortBy: 'timestamp', - extensions: [], - loading: true - } - ]); - const abortRef = useRef(new AbortController()); const searchTimerRef = useRef(); - useEffect(() => { - const EXCLUDED = new Set(['Other', 'SCM Providers', 'Extension Packs']); - const cats = Array.from(context.service.getCategories()) - .filter(c => !EXCLUDED.has(c)) - .sort((a, b) => a.localeCompare(b)); - setCategories(cats); - - const ac = abortRef.current; - sections.forEach((section, idx) => { - context.service - .search(ac, { - query: '', - category: '', - offset: 0, - size: 6, - sortBy: section.sortBy, - sortOrder: 'desc' as SortOrder - }) - .then(result => { - if (!isError(result)) { - setSections(prev => - prev.map((s, i) => - i === idx ? { ...s, extensions: (result as any).extensions ?? [], loading: false } : s - ) - ); - } - }) - .catch(() => setSections(prev => prev.map((s, i) => (i === idx ? { ...s, loading: false } : s)))); - }); - - return () => ac.abort(); - }, []); - const handleInputChange = (e: React.ChangeEvent) => { const val = e.target.value; setQuery(val); @@ -193,105 +150,54 @@ export const HomepageView: FunctionComponent = ({ onSearch }) }; return ( - - {/* ---- Hero ---- */} - - + + {SearchHeader && } + + + / + + - Open-source registry for VS Code–compatible editors - - - Find the right extension, -
- for any editor. -
- - Browse community-published extensions. Free, open, and vendor-neutral. - - - - - / + + + + + + + search - - - - - - - - search - - - - + + +
+ {popularSearches.length > 0 && ( = ({ onSearch }) sx={{ fontSize: '13px', color: 'text.disabled', alignSelf: 'center', flexShrink: 0 }}> Popular: - {POPULAR_CHIPS.map(chip => ( + {popularSearches.map(chip => ( onSearch(chip)} style={{ flexShrink: 0 }}> {chip} ))} -
+ )} +
+ ); +}; + +export const HomepageView: FunctionComponent = ({ onSearch }) => { + const { service, pageSettings } = useContext(MainContext); + const SearchHeader = pageSettings.elements.searchHeader; + const home = pageSettings.elements.home; + const popularSearches = home?.popularSearches ?? []; + const curatedSections = home?.curatedSections ?? DEFAULT_CURATED_SECTIONS; + const involvement = home?.involvement; + + const [categories, setCategories] = useState([]); + const [rows, setRows] = useState(() => + curatedSections.map(section => ({ ...section, extensions: [], loading: true })) + ); + + useEffect(() => { + setCategories( + Array.from(service.getCategories()) + .filter(c => !EXCLUDED_CATEGORIES.has(c)) + .sort((a, b) => a.localeCompare(b)) + ); + + const abortController = new AbortController(); + curatedSections.forEach((section, idx) => { + service + .search(abortController, { + query: '', + category: '', + offset: 0, + size: CURATED_SIZE, + sortBy: section.sortBy, + sortOrder: 'desc' as SortOrder + }) + .then(result => { + if (isError(result)) { + return; + } + const { extensions } = result as SearchResult; + setRows(prev => prev.map((row, i) => (i === idx ? { ...row, extensions, loading: false } : row))); + }) + .catch(() => setRows(prev => prev.map((row, i) => (i === idx ? { ...row, loading: false } : row)))); + }); + return () => abortController.abort(); + }, [service, curatedSections]); + + return ( + + - {/* ---- Categories ---- */} {categories.length > 0 && ( - - - Browse by category - - {/* Mobile: horizontal scrollable pills */} +
+ Browse by category - {categories.map(cat => { - const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; - return onSearch('', cat)} />; - })} + {categories.map(cat => ( + onSearch('', cat)} + /> + ))} - {/* Desktop: grid */} - {categories.map(cat => { - const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; - return onSearch('', cat)} />; - })} + {categories.map(cat => ( + onSearch('', cat)} + /> + ))} - +
)} - {/* ---- Curated rows ---- */} - {sections.map( - section => - !section.loading && - section.extensions.length > 0 && ( - + {rows.map( + row => + !row.loading && + row.extensions.length > 0 && ( +
= ({ onSearch }) fontWeight: 700, letterSpacing: '-0.02em' }}> - {section.title} + {row.title} = ({ onSearch }) color: 'text.disabled', display: { xs: 'none', sm: 'block' } }}> - {section.sub} + {row.subtitle} = ({ onSearch }) }, gap: '16px' }}> - {section.extensions.map((ext, idx) => ( + {row.extensions.map((ext, idx) => ( ))} - +
) )} - {/* ---- Get Involved ---- */} - - - Get Involved - - - {GET_INVOLVED_CARDS.map(card => ( - - + {involvement && involvement.cards.length > 0 && ( +
+ + {involvement.heading ?? 'Get Involved'} + + + {involvement.cards.map(card => ( + + + svg': { fontSize: 18 } + }}> + {card.icon} + + {card.title} + + + {card.description} + - + {card.label} - {card.title} - - - {card.desc} - - - {card.label} - - - ))} - - + + ))} + +
+ )}
); }; - -const GET_INVOLVED_CARDS = [ - { - icon: 'fork', - title: 'Contribute', - desc: 'Open VSX is fully open source. Help build the registry the ecosystem depends on.', - href: 'https://github.com/eclipse/openvsx', - label: 'View on GitHub →' - }, - { - icon: 'group', - title: 'Join the Working Group', - desc: 'Shape the future of an open, vendor-neutral marketplace for extensions.', - href: 'https://openvsxworkinggroup.github.io/', - label: 'Learn more →' - }, - { - icon: 'book', - title: 'Read the docs', - desc: 'Learn how to publish, claim namespaces, and consume extensions via the API.', - href: 'https://github.com/eclipse/openvsx/wiki', - label: 'Open documentation →' - } -]; - -const GetInvolvedIcon: FunctionComponent<{ icon: string }> = ({ icon }) => { - if (icon === 'fork') - return ( - - - - - - - ); - if (icon === 'group') - return ( - - - - - - - ); - return ( - - - - - ); -}; diff --git a/webui/src/pages/search/search-header.tsx b/webui/src/pages/search/search-header.tsx new file mode 100644 index 000000000..4dcc08bd6 --- /dev/null +++ b/webui/src/pages/search/search-header.tsx @@ -0,0 +1,114 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, KeyboardEvent } from 'react'; +import { Box, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; +import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; + +export const SearchHeader: FunctionComponent = props => { + const { sortBy, sortOrder, onSortByChanged, onSortOrderChanged } = props; + + const handleSortByChange = (event: SelectChangeEvent) => { + onSortByChanged(event.target.value as SortBy); + }; + + const toggleSortOrder = () => { + onSortOrderChanged(sortOrder === 'asc' ? 'desc' : 'asc'); + }; + + const title = props.searchQuery ? `"${props.searchQuery}"` : props.category || 'All extensions'; + + return ( + + + + {title} + + + {props.resultNumber.toLocaleString()} extensions found + + + + + Sort by + + + { + if (e.key === 'Enter') { + e.preventDefault(); + toggleSortOrder(); + } + }} + onClick={toggleSortOrder}> + {sortOrder === 'asc' ? ( + + ) : ( + + )} + + + + ); +}; + +export interface SearchHeaderProps { + onSortByChanged: (sb: SortBy) => void; + onSortOrderChanged: (so: SortOrder) => void; + sortBy: SortBy; + sortOrder: SortOrder; + resultNumber: number; + searchQuery?: string; + category?: ExtensionCategory | ''; +} diff --git a/webui/src/pages/search/search-page.tsx b/webui/src/pages/search/search-page.tsx new file mode 100644 index 000000000..325c77311 --- /dev/null +++ b/webui/src/pages/search/search-page.tsx @@ -0,0 +1,135 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent, useLayoutEffect } from 'react'; +import { Box } from '@mui/material'; +import { ExtensionCategory } from '../../extension-registry-types'; +import { useNavSearch } from '../../nav-search-context'; +import { ExtensionList } from '../../components/extension-list'; +import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; +import { CategoryPill } from '../../components/category-pill'; +import { CategoryListItem } from '../../components/category-list-item'; +import { Eyebrow } from '../../components/layout'; +import { useSearchFilter } from './use-search-filter'; +import { SearchHeader } from './search-header'; + +export const SearchPage: FunctionComponent = () => { + const { setIsHeroPage } = useNavSearch(); + const { + searchQuery, + category, + sortBy, + sortOrder, + debounceTime, + resultNumber, + categories, + onResultCount, + onCategoryChanged, + onSortByChanged, + onSortOrderChanged + } = useSearchFilter(); + + useLayoutEffect(() => { + setIsHeroPage(false); + // Entering the results should always start at the top, not wherever the + // hero/home page happened to be scrolled when the search was triggered. + window.scrollTo({ top: 0, left: 0 }); + return () => setIsHeroPage(false); + }, []); + + return ( + + {/* Mobile category pills — outside the flex row so negative-margin bleed isn't clipped */} + {categories.length > 0 && ( + + {(['', ...categories] as Array).map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return ( + onCategoryChanged(cat)} + /> + ); + })} + + )} + + + {/* Desktop categories sidebar */} + + Categories + {(['', ...categories] as Array).map(cat => { + const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; + return ( + onCategoryChanged(cat)} + /> + ); + })} + + + {/* Main content */} + + + + + + + ); +}; diff --git a/webui/src/pages/search/use-search-filter.ts b/webui/src/pages/search/use-search-filter.ts new file mode 100644 index 000000000..8c398d99a --- /dev/null +++ b/webui/src/pages/search/use-search-filter.ts @@ -0,0 +1,132 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; +import { useLocation } from 'react-router-dom'; +import { MainContext } from '../../context'; +import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import { useNavSearch } from '../../nav-search-context'; +import { addQuery } from '../../utils'; +import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; + +function buildSearchUrl(q: string, cat: ExtensionCategory | '', sb: SortBy, so: SortOrder): string { + const queries: { key: string; value: string }[] = []; + if (q) queries.push({ key: 'q', value: q }); + if (cat) queries.push({ key: 'category', value: cat }); + if (sb && sb !== 'relevance') queries.push({ key: 'sortBy', value: sb }); + if (so && so !== 'desc') queries.push({ key: 'sortOrder', value: so }); + return addQuery(ExtensionListRoutes.SEARCH, queries); +} + +export function useSearchFilter() { + const { search, state: locationState } = useLocation(); + const context = useContext(MainContext); + const { navQuery, setNavQuery, setSearchHandler } = useNavSearch(); + + // Navigation state is the most reliable fallback for values passed from HomePage + // (URL params may not yet be reflected in useLocation() during view transitions) + const navState = (locationState as { _q?: string; _cat?: string } | null) ?? {}; + + const [searchQuery, setSearchQuery] = useState(() => { + const fromUrl = new URLSearchParams(search).get('q') ?? ''; + return fromUrl || navQuery || navState._q || ''; + }); + const [category, setCategory] = useState(() => { + const fromUrl = (new URLSearchParams(search).get('category') as ExtensionCategory) ?? ''; + return fromUrl || (navState._cat as ExtensionCategory) || ''; + }); + const [sortBy, setSortBy] = useState( + () => (new URLSearchParams(search).get('sortBy') as SortBy) ?? 'relevance' + ); + const [sortOrder, setSortOrder] = useState( + () => (new URLSearchParams(search).get('sortOrder') as SortOrder) ?? 'desc' + ); + const [debounceTime, setDebounceTime] = useState(0); + const [resultNumber, setResultNumber] = useState(0); + const [categories, setCategories] = useState([]); + + // Kept current every render so the stable handler closure always dispatches to latest state + const handlerRef = useRef<(q: string) => void>(() => {}); + handlerRef.current = (q: string) => { + setSearchQuery(q); + setDebounceTime(1000); + history.replaceState(null, '', buildSearchUrl(q, category, sortBy, sortOrder)); + }; + + // Register this page as the nav bar's search handler; unregister on unmount + useLayoutEffect(() => { + const stable = (q: string) => handlerRef.current(q); + setSearchHandler(stable); + return () => setSearchHandler(null); + }, []); + + // On mount: if the URL has a query but navQuery is empty, push it to context so + // the navbar search bar reflects the current search + useLayoutEffect(() => { + if (!navQuery && searchQuery) setNavQuery(searchQuery); + }, []); + + // Keep local searchQuery in sync when the nav bar drives an external query change. + // Skip the initial mount: searchQuery is already seeded from the URL/navigation state, + // and the layout effect above pushes it into navQuery — reacting here on mount would + // wipe a URL-provided query (e.g. /search?q=vue) before navQuery has caught up. + const navSyncMounted = useRef(false); + useEffect(() => { + if (!navSyncMounted.current) { + navSyncMounted.current = true; + return; + } + if (navQuery === searchQuery) return; + setSearchQuery(navQuery); + setDebounceTime(500); + history.replaceState(null, '', buildSearchUrl(navQuery, category, sortBy, sortOrder)); + }, [navQuery]); + + // Load category list once on mount + useEffect(() => { + const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; + cats.sort((a, b) => { + if (a === b) return 0; + if (a === 'Other') return 1; + if (b === 'Other') return -1; + return a.localeCompare(b); + }); + setCategories(cats); + }, []); + + const updateURL = (q: string, cat: ExtensionCategory | '', sb?: SortBy, so?: SortOrder) => { + history.replaceState(null, '', buildSearchUrl(q, cat, sb ?? sortBy, so ?? sortOrder)); + }; + + const onCategoryChanged = (cat: ExtensionCategory | '') => { + setCategory(cat); + updateURL(searchQuery, cat); + }; + + const onSortByChanged = (sb: SortBy) => { + setSortBy(sb); + updateURL(searchQuery, category, sb, sortOrder); + }; + + const onSortOrderChanged = (so: SortOrder) => { + setSortOrder(so); + updateURL(searchQuery, category, sortBy, so); + }; + + return { + searchQuery, + category, + sortBy, + sortOrder, + debounceTime, + resultNumber, + categories, + onResultCount: setResultNumber, + onCategoryChanged, + onSortByChanged, + onSortOrderChanged + }; +} diff --git a/webui/src/utils.ts b/webui/src/utils.ts index 12bf5cc9a..6b10363f4 100644 --- a/webui/src/utils.ts +++ b/webui/src/utils.ts @@ -40,6 +40,16 @@ export function debounce(task: () => void, token: { timeout?: number }, delay: n token.timeout = window.setTimeout(task, delay); } +const compactNumberFormat = new Intl.NumberFormat(undefined, { + notation: 'compact', + compactDisplay: 'short' +} as Intl.NumberFormatOptions); + +/** Formats large counts compactly, e.g. 12345 -> "12K". */ +export function formatCompactNumber(value: number): string { + return compactNumberFormat.format(value); +} + export function toLocalTime(timestamp?: string): string | undefined { if (!timestamp) { return undefined; From 053f2bec190c3bece343b791cfca3a16dbd4dcad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 09:12:14 +0200 Subject: [PATCH 03/81] feat: adding some more shortcuts --- webui/src/layout/app-layout.tsx | 16 ++++++++++++-- webui/src/layout/nav-search-field.tsx | 24 +++++++++++++++++---- webui/src/pages/browse/use-browse-filter.ts | 2 +- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/webui/src/layout/app-layout.tsx b/webui/src/layout/app-layout.tsx index 0e38d5e40..ed1b4df3c 100644 --- a/webui/src/layout/app-layout.tsx +++ b/webui/src/layout/app-layout.tsx @@ -61,16 +61,28 @@ const AppLayoutContent: FunctionComponent = props => { useShortcut({ key: 'd', label: 'Go to documentation', - order: 20, + order: 2, callback: () => window.open('https://github.com/eclipse/openvsx/wiki', '_blank') }); useShortcut({ key: 'p', label: 'Publish extension', - order: 30, + order: 3, callback: () => navigate('/user-settings/extensions'), enabled: !!loginProviders }); + useShortcut({ + key: 'h', + label: 'Go to home', + order: 4, + callback: () => navigate('/') + }); + useShortcut({ + key: 's', + label: 'Go to search', + order: 5, + callback: () => navigate('/search') + }); const onDismissBannerButtonClick = () => { const onClose = pageSettings.elements.banner?.props?.onClose; diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx index 1c908f207..aa58b2734 100644 --- a/webui/src/layout/nav-search-field.tsx +++ b/webui/src/layout/nav-search-field.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, KeyboardEvent, useCallback, useRef } from 'react'; +import { FunctionComponent, KeyboardEvent, useCallback, useEffect, useRef } from 'react'; import { Box } from '@mui/material'; import { useNavigate } from 'react-router-dom'; import { ExtensionSearchfield } from '../components/extension-searchfield'; @@ -20,6 +20,17 @@ export const NavSearchField: FunctionComponent = () => { const searchHandlerRef = useRef(searchHandler); searchHandlerRef.current = searchHandler; + // On the hero (home) page the field is only a visual placeholder rendered at + // opacity 0 for the view-transition morph. Mark the subtree `inert` so it is + // removed from the tab order and the accessibility tree — otherwise keyboard + // users land on an invisible input. + const fieldRef = useRef(null); + useEffect(() => { + if (fieldRef.current) { + fieldRef.current.inert = isHeroPage; + } + }, [isHeroPage]); + const focusSearch = useCallback(() => { const hero = document.getElementById('hero-search-input') as HTMLInputElement | null; if (hero) { @@ -35,7 +46,7 @@ export const NavSearchField: FunctionComponent = () => { } }, []); - useShortcut({ key: '/', label: 'Focus search', order: 10, callback: focusSearch }); + useShortcut({ key: '/', label: 'Focus search', order: 1, callback: focusSearch }); const handleNavSearch = useCallback( (q: string) => { @@ -57,10 +68,14 @@ export const NavSearchField: FunctionComponent = () => { }, []); // ArrowDown moves focus from the search field into the results grid (first card), - // where two-axis arrow navigation takes over. + // where two-axis arrow navigation takes over. Escape blurs the field. const handleInputKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Escape') { + (e.target as HTMLInputElement).blur(); + return; + } if (e.key !== 'ArrowDown') return; - const firstCard = document.querySelector('a[data-ext-card]') as HTMLElement | null; + const firstCard = document.querySelector('[data-grid-item], a[data-ext-card]') as HTMLElement | null; if (firstCard) { e.preventDefault(); firstCard.focus(); @@ -77,6 +92,7 @@ export const NavSearchField: FunctionComponent = () => { px: { xs: '8px', md: '20px' } }}> Date: Thu, 2 Jul 2026 09:32:27 +0200 Subject: [PATCH 04/81] fix: removing some unnecessary hooks/contexts --- webui/src/layout/app-layout.tsx | 6 +- webui/src/layout/app-navbar.tsx | 6 +- webui/src/layout/nav-search-field.tsx | 8 +- webui/src/pages/browse/browse-header.tsx | 124 --------------- webui/src/pages/browse/browse-page.tsx | 142 ------------------ webui/src/pages/browse/use-browse-filter.ts | 139 ----------------- webui/src/pages/home/home-page.tsx | 14 +- webui/src/pages/home/homepage-view.tsx | 4 +- webui/src/pages/search/search-page.tsx | 9 +- webui/src/pages/search/use-search-filter.ts | 4 +- ...ch-context.tsx => search-sync-context.tsx} | 19 +-- 11 files changed, 29 insertions(+), 446 deletions(-) delete mode 100644 webui/src/pages/browse/browse-header.tsx delete mode 100644 webui/src/pages/browse/browse-page.tsx delete mode 100644 webui/src/pages/browse/use-browse-filter.ts rename webui/src/{nav-search-context.tsx => search-sync-context.tsx} (62%) diff --git a/webui/src/layout/app-layout.tsx b/webui/src/layout/app-layout.tsx index ed1b4df3c..cb6ad17e0 100644 --- a/webui/src/layout/app-layout.tsx +++ b/webui/src/layout/app-layout.tsx @@ -11,7 +11,7 @@ import { styled } from '@mui/material/styles'; import { Banner } from '../components/banner'; import { ShortcutsModal } from '../components/shortcuts-modal'; import { MainContext } from '../context'; -import { NavSearchProvider } from '../nav-search-context'; +import { SearchSyncProvider } from '../search-sync-context'; import { KeyboardShortcutsProvider } from '../keyboard-shortcuts-context'; import { useShortcut } from '../use-shortcut'; import { getCookieValueByKey, setCookie } from '../utils'; @@ -135,9 +135,9 @@ const AppLayoutContent: FunctionComponent = props => { export const AppLayout: FunctionComponent = props => ( - + - + ); diff --git a/webui/src/layout/app-navbar.tsx b/webui/src/layout/app-navbar.tsx index 4320ac1db..dbcf4f5c8 100644 --- a/webui/src/layout/app-navbar.tsx +++ b/webui/src/layout/app-navbar.tsx @@ -7,10 +7,10 @@ import { FunctionComponent, useContext, useEffect, useState } from 'react'; import { AppBar, Box, Toolbar } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; -import { Link as RouteLink } from 'react-router-dom'; +import { Link as RouteLink, useLocation } from 'react-router-dom'; import { HeaderMenu } from '../header-menu'; import { MainContext } from '../context'; -import { useNavSearch } from '../nav-search-context'; +import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; import { OpenVsxMark } from '../components/openvsx-mark'; import { NavSearchField } from './nav-search-field'; @@ -36,7 +36,7 @@ const BLUR_LAYERS = [ export const AppNavbar: FunctionComponent = () => { const { pageSettings } = useContext(MainContext); const { toolbarContent: ToolbarContent } = pageSettings.elements; - const { isHeroPage } = useNavSearch(); + const isHeroPage = useLocation().pathname === ExtensionListRoutes.MAIN; const theme = useTheme(); const navbg = theme.palette.mode === 'dark' ? 'rgba(14, 14, 20, 0.74)' : 'rgba(255, 255, 255, 0.78)'; const [scrolled, setScrolled] = useState(false); diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx index aa58b2734..2a61b4cd7 100644 --- a/webui/src/layout/nav-search-field.tsx +++ b/webui/src/layout/nav-search-field.tsx @@ -6,13 +6,15 @@ import { FunctionComponent, KeyboardEvent, useCallback, useEffect, useRef } from 'react'; import { Box } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { ExtensionSearchfield } from '../components/extension-searchfield'; -import { useNavSearch } from '../nav-search-context'; +import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; +import { useSearchSync } from '../search-sync-context'; import { useShortcut } from '../use-shortcut'; export const NavSearchField: FunctionComponent = () => { - const { isHeroPage, navQuery, setNavQuery, searchHandler } = useNavSearch(); + const isHeroPage = useLocation().pathname === ExtensionListRoutes.MAIN; + const { navQuery, setNavQuery, searchHandler } = useSearchSync(); const navigate = useNavigate(); // Keep a ref so handleNavSearch always calls the latest handler without needing diff --git a/webui/src/pages/browse/browse-header.tsx b/webui/src/pages/browse/browse-header.tsx deleted file mode 100644 index 630a36b8b..000000000 --- a/webui/src/pages/browse/browse-header.tsx +++ /dev/null @@ -1,124 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { FunctionComponent, KeyboardEvent, useState, useEffect } from 'react'; -import { Box, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; -import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; - -export const BrowseHeader: FunctionComponent = props => { - const [sortBy, setSortBy] = useState(props.sortBy); - const [sortOrder, setSortOrder] = useState(props.sortOrder); - - useEffect(() => { - setSortBy(props.sortBy); - setSortOrder(props.sortOrder); - }, [props.sortBy, props.sortOrder]); - - const handleSortByChange = (event: SelectChangeEvent) => { - const sb = event.target.value as SortBy; - setSortBy(sb); - props.onSortByChanged(sb); - }; - - const handleSortOrderChange = () => { - const newOrder = sortOrder === 'asc' ? 'desc' : 'asc'; - setSortOrder(newOrder); - props.onSortOrderChanged(newOrder); - }; - - const title = props.searchQuery ? `"${props.searchQuery}"` : props.category || 'All extensions'; - - return ( - - - - {title} - - - {props.resultNumber.toLocaleString()} extensions found - - - - - Sort by - - - { - if (e.key === 'Enter') { - e.preventDefault(); - handleSortOrderChange(); - } - }} - onClick={handleSortOrderChange}> - {sortOrder === 'asc' ? ( - - ) : ( - - )} - - - - ); -}; - -export interface BrowseHeaderProps { - onSortByChanged: (sb: SortBy) => void; - onSortOrderChanged: (so: SortOrder) => void; - sortBy: SortBy; - sortOrder: SortOrder; - resultNumber: number; - searchQuery?: string; - category?: ExtensionCategory | ''; -} diff --git a/webui/src/pages/browse/browse-page.tsx b/webui/src/pages/browse/browse-page.tsx deleted file mode 100644 index 2634504dc..000000000 --- a/webui/src/pages/browse/browse-page.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { FunctionComponent, useLayoutEffect } from 'react'; -import { Box, Typography } from '@mui/material'; -import { ExtensionCategory } from '../../extension-registry-types'; -import { useNavSearch } from '../../nav-search-context'; -import { ExtensionList } from '../../components/extension-list'; -import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; -import { CategoryPill } from '../../components/category-pill'; -import { CategoryListItem } from '../../components/category-list-item'; -import { useBrowseFilter } from './use-browse-filter'; -import { BrowseHeader } from './browse-header'; - -export const BrowsePage: FunctionComponent = () => { - const { setIsHeroPage } = useNavSearch(); - const { - searchQuery, - category, - sortBy, - sortOrder, - debounceTime, - resultNumber, - categories, - onResultCount, - onCategoryChanged, - onSortByChanged, - onSortOrderChanged - } = useBrowseFilter(); - - useLayoutEffect(() => { - setIsHeroPage(false); - return () => setIsHeroPage(false); - }, []); - - return ( - - {/* Mobile category pills — outside the flex row so negative-margin bleed isn't clipped */} - {categories.length > 0 && ( - - {(['', ...categories] as Array).map(cat => { - const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; - return ( - onCategoryChanged(cat)} - /> - ); - })} - - )} - - - {/* Desktop categories sidebar */} - - - Categories - - {(['', ...categories] as Array).map(cat => { - const Icon = CATEGORY_ICONS[cat] ?? DefaultCategoryIcon; - return ( - onCategoryChanged(cat)} - /> - ); - })} - - - {/* Main content */} - - - - - - - ); -}; diff --git a/webui/src/pages/browse/use-browse-filter.ts b/webui/src/pages/browse/use-browse-filter.ts deleted file mode 100644 index 286714744..000000000 --- a/webui/src/pages/browse/use-browse-filter.ts +++ /dev/null @@ -1,139 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { MainContext } from '../../context'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; -import { useNavSearch } from '../../nav-search-context'; -import { addQuery } from '../../utils'; -import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; - -function buildBrowseUrl(q: string, cat: ExtensionCategory | '', sb: SortBy, so: SortOrder): string { - const queries: { key: string; value: string }[] = []; - if (q) queries.push({ key: 'q', value: q }); - if (cat) queries.push({ key: 'category', value: cat }); - if (sb && sb !== 'relevance') queries.push({ key: 'sortBy', value: sb }); - if (so && so !== 'desc') queries.push({ key: 'sortOrder', value: so }); - return addQuery(ExtensionListRoutes.SEARCH, queries); -} - -export function useBrowseFilter() { - const { search, state: locationState } = useLocation(); - const context = useContext(MainContext); - const { navQuery, setNavQuery, setSearchHandler } = useNavSearch(); - - // Navigation state is the most reliable fallback for values passed from HomePage - // (URL params may not yet be reflected in useLocation() during view transitions) - const navState = (locationState as { _q?: string; _cat?: string } | null) ?? {}; - - const [searchQuery, setSearchQuery] = useState(() => { - const fromUrl = new URLSearchParams(search).get('q') ?? ''; - return fromUrl || navQuery || navState._q || ''; - }); - const [category, setCategory] = useState(() => { - const fromUrl = (new URLSearchParams(search).get('category') as ExtensionCategory) ?? ''; - return fromUrl || (navState._cat as ExtensionCategory) || ''; - }); - const [sortBy, setSortBy] = useState( - () => (new URLSearchParams(search).get('sortBy') as SortBy) ?? 'relevance' - ); - const [sortOrder, setSortOrder] = useState( - () => (new URLSearchParams(search).get('sortOrder') as SortOrder) ?? 'desc' - ); - const [debounceTime, setDebounceTime] = useState(0); - const [resultNumber, setResultNumber] = useState(0); - const [categories, setCategories] = useState([]); - - // Kept current every render so the stable handler closure always dispatches to latest state - const handlerRef = useRef<(q: string) => void>(() => {}); - handlerRef.current = (q: string) => { - setSearchQuery(q); - setDebounceTime(1000); - history.replaceState(null, '', buildBrowseUrl(q, category, sortBy, sortOrder)); - }; - - // Register this page as the nav bar's search handler; unregister on unmount - useLayoutEffect(() => { - const stable = (q: string) => handlerRef.current(q); - setSearchHandler(stable); - return () => setSearchHandler(null); - }, []); - - // On mount: if the URL has a query but navQuery is empty, push it to context so - // the navbar search bar reflects the current search - useLayoutEffect(() => { - if (!navQuery && searchQuery) setNavQuery(searchQuery); - }, []); - - // Keep local searchQuery in sync when the nav bar drives an external query change. - // Skip the initial mount: searchQuery is already seeded from the URL/navigation state, - // and the layout effect above pushes it into navQuery — reacting here on mount would - // wipe a URL-provided query (e.g. /browse?q=vue) before navQuery has caught up. - const navSyncMounted = useRef(false); - useEffect(() => { - if (!navSyncMounted.current) { - navSyncMounted.current = true; - return; - } - if (navQuery === searchQuery) return; - setSearchQuery(navQuery); - setDebounceTime(500); - history.replaceState(null, '', buildBrowseUrl(navQuery, category, sortBy, sortOrder)); - }, [navQuery]); - - // Load category list once on mount - useEffect(() => { - const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; - cats.sort((a, b) => { - if (a === b) return 0; - if (a === 'Other') return 1; - if (b === 'Other') return -1; - return a.localeCompare(b); - }); - setCategories(cats); - }, []); - - const updateURL = (q: string, cat: ExtensionCategory | '', sb?: SortBy, so?: SortOrder) => { - history.replaceState(null, '', buildBrowseUrl(q, cat, sb ?? sortBy, so ?? sortOrder)); - }; - - const onSearchChanged = (q: string) => { - setSearchQuery(q); - setDebounceTime(1000); - updateURL(q, category); - }; - - const onCategoryChanged = (cat: ExtensionCategory | '') => { - setCategory(cat); - updateURL(searchQuery, cat); - }; - - const onSortByChanged = (sb: SortBy) => { - setSortBy(sb); - updateURL(searchQuery, category, sb, sortOrder); - }; - - const onSortOrderChanged = (so: SortOrder) => { - setSortOrder(so); - updateURL(searchQuery, category, sortBy, so); - }; - - return { - searchQuery, - category, - sortBy, - sortOrder, - debounceTime, - resultNumber, - categories, - onResultCount: setResultNumber, - onSearchChanged, - onCategoryChanged, - onSortByChanged, - onSortOrderChanged - }; -} diff --git a/webui/src/pages/home/home-page.tsx b/webui/src/pages/home/home-page.tsx index 638370ab3..4f0cbbc22 100644 --- a/webui/src/pages/home/home-page.tsx +++ b/webui/src/pages/home/home-page.tsx @@ -4,23 +4,18 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useCallback, useLayoutEffect } from 'react'; +import { FunctionComponent, useCallback } from 'react'; import { flushSync } from 'react-dom'; import { useNavigate } from 'react-router-dom'; -import { useNavSearch } from '../../nav-search-context'; +import { useSearchSync } from '../../search-sync-context'; import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; import { addQuery } from '../../utils'; import { HomepageView } from './homepage-view'; export const HomePage: FunctionComponent = () => { - const { setIsHeroPage, setNavQuery } = useNavSearch(); + const { setNavQuery } = useSearchSync(); const navigate = useNavigate(); - useLayoutEffect(() => { - setIsHeroPage(true); - return () => setIsHeroPage(false); - }, []); - const focusNavSearch = useCallback(() => { const input = document.getElementById('search-input') as HTMLInputElement | null; // preventScroll: a plain focus() scrolls the input into view, which on mobile @@ -38,7 +33,6 @@ export const HomePage: FunctionComponent = () => { const run = () => { setNavQuery(query); - setIsHeroPage(false); navigate(url, { state: { _q: query, _cat: cat } }); }; @@ -60,7 +54,7 @@ export const HomePage: FunctionComponent = () => { if (shouldFocus) focusNavSearch(); } }, - [navigate, setNavQuery, setIsHeroPage, focusNavSearch] + [navigate, setNavQuery, focusNavSearch] ); return ; diff --git a/webui/src/pages/home/homepage-view.tsx b/webui/src/pages/home/homepage-view.tsx index bb3c278f0..6d059c9bb 100644 --- a/webui/src/pages/home/homepage-view.tsx +++ b/webui/src/pages/home/homepage-view.tsx @@ -8,7 +8,7 @@ import { ComponentType, FunctionComponent, useContext, useEffect, useRef, useSta import { Box, ButtonBase, Typography } from '@mui/material'; import { styled, alpha } from '@mui/material/styles'; import { MainContext } from '../../context'; -import { useNavSearch } from '../../nav-search-context'; +import { useSearchSync } from '../../search-sync-context'; import { ExtensionCard } from '../../components/extension-card'; import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; import { HomeCuratedSection } from '../../page-settings'; @@ -130,7 +130,7 @@ interface HeroSearchProps { * field, not the whole homepage (categories grid and curated rows). */ const HeroSearch: FunctionComponent = ({ onSearch, searchHeader: SearchHeader, popularSearches }) => { - const { navQuery } = useNavSearch(); + const { navQuery } = useSearchSync(); const [query, setQuery] = useState(() => navQuery); const searchTimerRef = useRef(); diff --git a/webui/src/pages/search/search-page.tsx b/webui/src/pages/search/search-page.tsx index 325c77311..03f3af1cb 100644 --- a/webui/src/pages/search/search-page.tsx +++ b/webui/src/pages/search/search-page.tsx @@ -7,7 +7,6 @@ import { FunctionComponent, useLayoutEffect } from 'react'; import { Box } from '@mui/material'; import { ExtensionCategory } from '../../extension-registry-types'; -import { useNavSearch } from '../../nav-search-context'; import { ExtensionList } from '../../components/extension-list'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; @@ -17,7 +16,6 @@ import { useSearchFilter } from './use-search-filter'; import { SearchHeader } from './search-header'; export const SearchPage: FunctionComponent = () => { - const { setIsHeroPage } = useNavSearch(); const { searchQuery, category, @@ -33,11 +31,9 @@ export const SearchPage: FunctionComponent = () => { } = useSearchFilter(); useLayoutEffect(() => { - setIsHeroPage(false); // Entering the results should always start at the top, not wherever the // hero/home page happened to be scrolled when the search was triggered. window.scrollTo({ top: 0, left: 0 }); - return () => setIsHeroPage(false); }, []); return ( @@ -92,10 +88,11 @@ export const SearchPage: FunctionComponent = () => { pb: '40px', display: { xs: 'none', md: 'block' }, position: 'sticky', - top: '72px', + top: '60px', alignSelf: 'flex-start', maxHeight: 'calc(100vh - 80px)', - overflowY: 'auto' + overflowY: 'auto', + zIndex: 50 }}> Categories {(['', ...categories] as Array).map(cat => { diff --git a/webui/src/pages/search/use-search-filter.ts b/webui/src/pages/search/use-search-filter.ts index 8c398d99a..74e161b67 100644 --- a/webui/src/pages/search/use-search-filter.ts +++ b/webui/src/pages/search/use-search-filter.ts @@ -8,7 +8,7 @@ import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react' import { useLocation } from 'react-router-dom'; import { MainContext } from '../../context'; import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; -import { useNavSearch } from '../../nav-search-context'; +import { useSearchSync } from '../../search-sync-context'; import { addQuery } from '../../utils'; import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; @@ -24,7 +24,7 @@ function buildSearchUrl(q: string, cat: ExtensionCategory | '', sb: SortBy, so: export function useSearchFilter() { const { search, state: locationState } = useLocation(); const context = useContext(MainContext); - const { navQuery, setNavQuery, setSearchHandler } = useNavSearch(); + const { navQuery, setNavQuery, setSearchHandler } = useSearchSync(); // Navigation state is the most reliable fallback for values passed from HomePage // (URL params may not yet be reflected in useLocation() during view transitions) diff --git a/webui/src/nav-search-context.tsx b/webui/src/search-sync-context.tsx similarity index 62% rename from webui/src/nav-search-context.tsx rename to webui/src/search-sync-context.tsx index 8ed689d34..d5dc69442 100644 --- a/webui/src/nav-search-context.tsx +++ b/webui/src/search-sync-context.tsx @@ -6,26 +6,21 @@ import { createContext, FunctionComponent, ReactNode, useCallback, useContext, useMemo, useState } from 'react'; -interface NavSearchContextValue { - isHeroPage: boolean; - setIsHeroPage: (b: boolean) => void; +interface SearchSyncContextValue { navQuery: string; setNavQuery: (q: string) => void; searchHandler: ((q: string) => void) | null; setSearchHandler: (fn: ((q: string) => void) | null) => void; } -const NavSearchContext = createContext({ - isHeroPage: false, - setIsHeroPage: () => {}, +const SearchSyncContext = createContext({ navQuery: '', setNavQuery: () => {}, searchHandler: null, setSearchHandler: () => {} }); -export const NavSearchProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { - const [isHeroPage, setIsHeroPage] = useState(false); +export const SearchSyncProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { const [navQuery, setNavQuery] = useState(''); // useState treats any function passed directly as a reducer — wrap with () => fn to store functions correctly const [searchHandler, setSearchHandlerState] = useState<((q: string) => void) | null>(null); @@ -33,11 +28,11 @@ export const NavSearchProvider: FunctionComponent<{ children: ReactNode }> = ({ setSearchHandlerState(() => fn); }, []); const value = useMemo( - () => ({ isHeroPage, setIsHeroPage, navQuery, setNavQuery, searchHandler, setSearchHandler }), - [isHeroPage, navQuery, searchHandler, setSearchHandler] + () => ({ navQuery, setNavQuery, searchHandler, setSearchHandler }), + [navQuery, searchHandler, setSearchHandler] ); - return {children}; + return {children}; }; // eslint-disable-next-line react-refresh/only-export-components -export const useNavSearch = () => useContext(NavSearchContext); +export const useSearchSync = () => useContext(SearchSyncContext); From 1b58875aa1db3ca13f7205b8422cafd7ef5df1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 12:16:41 +0200 Subject: [PATCH 05/81] refactor: improving search implementation following react good practices --- webui/src/components/extension-list.tsx | 57 +-- .../src/components/extension-searchfield.tsx | 165 ++++--- .../use-grid-keyboard-navigation.ts | 29 +- webui/src/context/search/search-context.tsx | 27 ++ .../context/search/search-focus-context.tsx | 46 ++ webui/src/hooks/use-debounced-callback.ts | 24 +- webui/src/hooks/use-search-focus.ts | 17 + webui/src/hooks/use-search.ts | 73 +++ webui/src/hooks/use-signal-effect.ts | 29 ++ webui/src/hooks/use-signal.ts | 27 ++ webui/src/layout/app-layout.tsx | 11 +- webui/src/layout/nav-search-field.tsx | 99 ++-- webui/src/pages/home/browse-categories.tsx | 66 +++ webui/src/pages/home/curated-sections.tsx | 88 ++++ webui/src/pages/home/get-involved.tsx | 90 ++++ webui/src/pages/home/hero-search.tsx | 250 ++++++++++ webui/src/pages/home/home-page.tsx | 79 ++- webui/src/pages/home/homepage-view.tsx | 448 ------------------ webui/src/pages/home/use-home-data.ts | 75 +++ webui/src/pages/search/search-page.tsx | 51 +- webui/src/pages/search/use-categories.ts | 27 ++ webui/src/pages/search/use-search-filter.ts | 132 ------ webui/src/search-sync-context.tsx | 38 -- 23 files changed, 1080 insertions(+), 868 deletions(-) create mode 100644 webui/src/context/search/search-context.tsx create mode 100644 webui/src/context/search/search-focus-context.tsx create mode 100644 webui/src/hooks/use-search-focus.ts create mode 100644 webui/src/hooks/use-search.ts create mode 100644 webui/src/hooks/use-signal-effect.ts create mode 100644 webui/src/hooks/use-signal.ts create mode 100644 webui/src/pages/home/browse-categories.tsx create mode 100644 webui/src/pages/home/curated-sections.tsx create mode 100644 webui/src/pages/home/get-involved.tsx create mode 100644 webui/src/pages/home/hero-search.tsx delete mode 100644 webui/src/pages/home/homepage-view.tsx create mode 100644 webui/src/pages/home/use-home-data.ts create mode 100644 webui/src/pages/search/use-categories.ts delete mode 100644 webui/src/pages/search/use-search-filter.ts delete mode 100644 webui/src/search-sync-context.tsx diff --git a/webui/src/components/extension-list.tsx b/webui/src/components/extension-list.tsx index 14e34834b..1c18a2b4f 100644 --- a/webui/src/components/extension-list.tsx +++ b/webui/src/components/extension-list.tsx @@ -41,37 +41,33 @@ export const ExtensionList: FunctionComponent = props => { useEffect(() => { filterSize.current = props.filter.size ?? filterSize.current; - debounce( - async () => { - try { - const result = await context.service.search(abortController.current, props.filter); - if (isError(result)) { - throw result; - } - - const searchResult = result as SearchResult; - props.onUpdate(searchResult.totalSize); - const actualSize = searchResult.extensions.length; - pageOffset.current = lastRequestedPage.current; - const extensionKeys = new Set(); - for (const ext of searchResult.extensions) { - extensionKeys.add(`${ext.namespace}.${ext.name}`); - } + debounce(async () => { + try { + const result = await context.service.search(abortController.current, props.filter); + if (isError(result)) { + throw result; + } - setExtensions(searchResult.extensions); - setExtensionKeys(extensionKeys); - setAppliedFilter(props.filter); - setHasMore(actualSize < searchResult.totalSize && actualSize > 0); - } catch (err) { - context.handleError(err); - } finally { - setLoading(false); + const searchResult = result as SearchResult; + props.onUpdate(searchResult.totalSize); + const actualSize = searchResult.extensions.length; + pageOffset.current = lastRequestedPage.current; + const extensionKeys = new Set(); + for (const ext of searchResult.extensions) { + extensionKeys.add(`${ext.namespace}.${ext.name}`); } - }, - cancellationToken.current, - props.debounceTime - ); - }, [props.filter.category, props.filter.query, props.filter.sortBy, props.filter.sortOrder, props.debounceTime]); + + setExtensions(searchResult.extensions); + setExtensionKeys(extensionKeys); + setAppliedFilter(props.filter); + setHasMore(actualSize < searchResult.totalSize && actualSize > 0); + } catch (err) { + context.handleError(err); + } finally { + setLoading(false); + } + }, cancellationToken.current); + }, [props.filter.category, props.filter.query, props.filter.sortBy, props.filter.sortOrder]); const loadMore = async (p: number): Promise => { setLoading(true); @@ -153,7 +149,7 @@ export const ExtensionList: FunctionComponent = props => { onFocus={grid.onFocus} sx={{ display: 'grid', - gridTemplateColumns: { xs: 'repeat(2, 1fr)', sm: 'repeat(auto-fill, minmax(190px, 1fr))' }, + gridTemplateColumns: { xs: 'repeat(2, 1fr)', sm: 'repeat(auto-fill, minmax(175px, 1fr))' }, gap: '16px' }}> {extensionList} @@ -165,6 +161,5 @@ export const ExtensionList: FunctionComponent = props => { export interface ExtensionListProps { filter: ExtensionFilter; - debounceTime: number; onUpdate: (resultNumber: number) => void; } diff --git a/webui/src/components/extension-searchfield.tsx b/webui/src/components/extension-searchfield.tsx index d356e690a..95348dc8f 100644 --- a/webui/src/components/extension-searchfield.tsx +++ b/webui/src/components/extension-searchfield.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { ChangeEvent, FunctionComponent, KeyboardEvent, useRef } from 'react'; +import { ChangeEvent, ForwardedRef, forwardRef, KeyboardEvent, useCallback, useRef } from 'react'; import SearchIcon from '@mui/icons-material/Search'; import ClearIcon from '@mui/icons-material/Close'; import { IconButton, InputBase, Box } from '@mui/material'; @@ -65,80 +65,97 @@ const SearchInput = styled(InputBase)(({ theme }) => ({ '& input::-webkit-search-cancel-button': { display: 'none' } })); -export const ExtensionSearchfield: FunctionComponent = props => { - const inputRef = useRef(null); +export const ExtensionSearchfield = forwardRef( + (props: ExtensionSearchfieldProps, ref: ForwardedRef) => { + const inputRef = useRef(null); - const handleSearchChange = (event: ChangeEvent) => { - props.onSearchChanged(event.target.value); - }; + // Keep the forwarded ref and the internal ref (used by the clear button) in sync. + const setInputRef = useCallback( + (node: HTMLInputElement | null) => { + inputRef.current = node; + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } + }, + [ref] + ); - const handleSearchButtonClick = () => { - if (props.onSearchSubmit) { - props.onSearchSubmit(props.searchQuery ?? ''); - } - }; + const handleSearchChange = (event: ChangeEvent) => { + props.onSearchChanged(event.target.value); + }; - const handleClear = () => { - props.onSearchChanged(''); - inputRef.current?.focus(); - }; + const handleSearchButtonClick = () => { + if (props.onSearchSubmit) { + props.onSearchSubmit(props.searchQuery ?? ''); + } + }; - return ( - - / - { - if (e.key === 'Enter' && props.onSearchSubmit) { - props.onSearchSubmit(props.searchQuery ?? ''); - } - }} - /> - - {props.searchQuery && ( - - - - )} - {!props.hideIconButton && ( - - - - )} - - ); -}; + const handleClear = () => { + props.onSearchChanged(''); + inputRef.current?.focus(); + }; + + return ( + + / + { + if (e.key === 'Enter' && props.onSearchSubmit) { + props.onSearchSubmit(props.searchQuery ?? ''); + } + }} + /> + + {props.searchQuery && ( + + + + )} + {!props.hideIconButton && ( + + + + )} + + ); + } +); + +ExtensionSearchfield.displayName = 'ExtensionSearchfield'; diff --git a/webui/src/components/use-grid-keyboard-navigation.ts b/webui/src/components/use-grid-keyboard-navigation.ts index 1b8b7bbab..0912f6816 100644 --- a/webui/src/components/use-grid-keyboard-navigation.ts +++ b/webui/src/components/use-grid-keyboard-navigation.ts @@ -5,19 +5,12 @@ *****************************************************************************/ import { FocusEvent, KeyboardEvent, RefObject, useCallback, useEffect, useRef } from 'react'; +import { useSearchFocus } from '../hooks/use-search-focus'; +import { useSignalEffect } from '../hooks/use-signal-effect'; const ITEM_SELECTOR = 'a[data-ext-card]'; const NAV_KEYS = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End']; -function focusSearchInput(): void { - const hero = document.getElementById('hero-search-input') as HTMLInputElement | null; - const nav = document.getElementById('search-input') as HTMLInputElement | null; - const target = hero ?? nav; - if (!target) return; - target.focus(); - requestAnimationFrame(() => target.setSelectionRange(target.value.length, target.value.length)); -} - interface GridKeyboardNavigation { containerRef: RefObject; onKeyDown: (event: KeyboardEvent) => void; @@ -35,6 +28,7 @@ interface GridKeyboardNavigation { */ export function useGridKeyboardNavigation(): GridKeyboardNavigation { const containerRef = useRef(null); + const { focusSearch, focusResultsSignal } = useSearchFocus(); const getItems = useCallback((): HTMLElement[] => { const container = containerRef.current; @@ -77,6 +71,19 @@ export function useGridKeyboardNavigation(): GridKeyboard return () => observer.disconnect(); }, [getItems, setRovingTabIndex]); + // Focus the first card when the search field asks to hand off (ArrowDown). + useSignalEffect( + focusResultsSignal, + useCallback(() => { + const items = getItems(); + if (items.length === 0) { + return; + } + setRovingTabIndex(items, 0); + items[0].focus(); + }, [getItems, setRovingTabIndex]) + ); + const onKeyDown = useCallback( (event: KeyboardEvent): void => { if (!NAV_KEYS.includes(event.key)) return; @@ -104,7 +111,7 @@ export function useGridKeyboardNavigation(): GridKeyboard case 'ArrowUp': if (currentIndex < cols) { event.preventDefault(); - focusSearchInput(); + focusSearch(); return; } nextIndex = currentIndex - cols; @@ -123,7 +130,7 @@ export function useGridKeyboardNavigation(): GridKeyboard items[nextIndex].focus(); } }, - [getItems, getColumnCount, setRovingTabIndex] + [getItems, getColumnCount, setRovingTabIndex, focusSearch] ); const onFocus = useCallback( diff --git a/webui/src/context/search/search-context.tsx b/webui/src/context/search/search-context.tsx new file mode 100644 index 000000000..cde0d35fe --- /dev/null +++ b/webui/src/context/search/search-context.tsx @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { createContext, FunctionComponent, ReactNode, useMemo, useState } from 'react'; + +export interface SearchContextValue { + query: string; + setQuery: (q: string) => void; +} + +// eslint-disable-next-line react-refresh/only-export-components +export const SearchContext = createContext({ + query: '', + setQuery: () => {} +}); + +// Holds the persisted search query so the search fields stay in sync as the user +// navigates. The `search` action lives in useSearch; focus coordination lives in +// SearchFocusContext. +export const SearchProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { + const [query, setQuery] = useState(''); + const value = useMemo(() => ({ query, setQuery }), [query]); + return {children}; +}; diff --git a/webui/src/context/search/search-focus-context.tsx b/webui/src/context/search/search-focus-context.tsx new file mode 100644 index 000000000..6c9bee975 --- /dev/null +++ b/webui/src/context/search/search-focus-context.tsx @@ -0,0 +1,46 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { createContext, FunctionComponent, ReactNode, useMemo } from 'react'; +import { useSignal } from '../../hooks/use-signal'; + +/** + * Focus coordination between the search fields and the results grid. Signals are + * bumped by whichever component owns the intent; subscribers react via + * useSignalEffect and focus their own element, so no entry point needs a global + * DOM lookup. + */ +export interface SearchFocusContextValue { + // Ask the active search field (hero on the home page, nav bar elsewhere) to focus. + focusSearchSignal: number; + focusSearch: () => void; + // Ask the results grid to focus its first item. + focusResultsSignal: number; + focusResults: () => void; +} + +// eslint-disable-next-line react-refresh/only-export-components +export const SearchFocusContext = createContext({ + focusSearchSignal: 0, + focusSearch: () => {}, + focusResultsSignal: 0, + focusResults: () => {} +}); + +export const SearchFocusProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { + const focusSearch = useSignal(); + const focusResults = useSignal(); + const value = useMemo( + () => ({ + focusSearchSignal: focusSearch.signal, + focusSearch: focusSearch.emit, + focusResultsSignal: focusResults.signal, + focusResults: focusResults.emit + }), + [focusSearch, focusResults] + ); + return {children}; +}; diff --git a/webui/src/hooks/use-debounced-callback.ts b/webui/src/hooks/use-debounced-callback.ts index 51bff5d83..b070301dc 100644 --- a/webui/src/hooks/use-debounced-callback.ts +++ b/webui/src/hooks/use-debounced-callback.ts @@ -15,22 +15,40 @@ import { useMemo, useRef } from 'react'; const DEFAULT_DELAY_MS = 300; +export interface DebouncedCallback void> { + (...args: Parameters): void; + // Drop any pending invocation. + cancel: () => void; +} + /** * Returns a stable function that delays invoking `callback` until `delay` ms * have elapsed since the last call. Always invokes the most recent `callback`. + * The returned function also exposes `cancel` to drop a pending invocation. */ export function useDebouncedCallback void>( callback: T, delay: number = DEFAULT_DELAY_MS -): (...args: Parameters) => void { +): DebouncedCallback { const callbackRef = useRef(callback); callbackRef.current = callback; return useMemo(() => { let timer: ReturnType | undefined; - return (...args: Parameters) => { + + const debounced = ((...args: Parameters) => { clearTimeout(timer); - timer = setTimeout(() => callbackRef.current(...args), delay); + timer = setTimeout(() => { + timer = undefined; + callbackRef.current(...args); + }, delay); + }) as DebouncedCallback; + + debounced.cancel = () => { + clearTimeout(timer); + timer = undefined; }; + + return debounced; }, [delay]); } diff --git a/webui/src/hooks/use-search-focus.ts b/webui/src/hooks/use-search-focus.ts new file mode 100644 index 000000000..978d145a9 --- /dev/null +++ b/webui/src/hooks/use-search-focus.ts @@ -0,0 +1,17 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useContext } from 'react'; +import { SearchFocusContext, SearchFocusContextValue } from '../context/search/search-focus-context'; + +/** + * Access the search focus-coordination signals. Emit with `focusSearch()` / + * `focusResults()`; subscribe to `focusSearchSignal` / `focusResultsSignal` + * through useSignalEffect. + */ +export function useSearchFocus(): SearchFocusContextValue { + return useContext(SearchFocusContext); +} diff --git a/webui/src/hooks/use-search.ts b/webui/src/hooks/use-search.ts new file mode 100644 index 000000000..dabceb7ec --- /dev/null +++ b/webui/src/hooks/use-search.ts @@ -0,0 +1,73 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useCallback, useContext, useEffect } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { SearchContext } from '../context/search/search-context'; +import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; +import { ExtensionCategory, SortBy, SortOrder } from '../extension-registry-types'; +import { addQuery } from '../utils'; + +export interface SearchFilter { + query: string; + category: (ExtensionCategory | '') & string; + sortBy: SortBy; + sortOrder: SortOrder; +} + +// Write only non-default values so shared links stay clean. +export function filterToParams({ query, category, sortBy, sortOrder }: SearchFilter): Record { + const params: Record = {}; + if (query) params.q = query; + if (category) params.category = category; + if (sortBy !== 'relevance') params.sortBy = sortBy; + if (sortOrder !== 'desc') params.sortOrder = sortOrder; + return params; +} + +export function useSearch() { + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const { query, setQuery } = useContext(SearchContext); + + const filter: SearchFilter = { + query: searchParams.get('q') ?? '', + category: (searchParams.get('category') as ExtensionCategory) ?? '', + sortBy: (searchParams.get('sortBy') as SortBy) ?? 'relevance', + sortOrder: (searchParams.get('sortOrder') as SortOrder) ?? 'desc' + }; + + // Keep nav bar in sync with URL query param changes (back/forward, shared links, category tiles). + useEffect(() => { + setQuery(filter.query); + }, [filter.query, setQuery]); + + // On the search page: patch URL params in place (replace, no new history entry). + // From anywhere else: navigate to the search route. + const search = useCallback( + (patch: Partial) => { + const next = { ...filter, ...patch }; + + const { pathname } = window.location; + if (pathname === ExtensionListRoutes.SEARCH) { + setSearchParams(filterToParams(next), { replace: true }); + return; + } + + const fromHome = pathname === ExtensionListRoutes.MAIN; + const url = addQuery(ExtensionListRoutes.SEARCH, [ + { key: 'q', value: next.query || undefined }, + { key: 'category', value: next.category || undefined }, + { key: 'sortBy', value: next.sortBy !== 'relevance' ? next.sortBy : undefined }, + { key: 'sortOrder', value: next.sortOrder !== 'desc' ? next.sortOrder : undefined } + ]); + navigate(url, { replace: !fromHome }); + }, + [navigate, filter, setSearchParams] + ); + + return { query, setQuery, search, filter }; +} diff --git a/webui/src/hooks/use-signal-effect.ts b/webui/src/hooks/use-signal-effect.ts new file mode 100644 index 000000000..72df7c0ac --- /dev/null +++ b/webui/src/hooks/use-signal-effect.ts @@ -0,0 +1,29 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useLayoutEffect, useRef } from 'react'; + +/** + * Runs `effect` whenever `signal` changes to a new value, skipping the initial + * mount. Used to coordinate imperative focus across components through a + * monotonically increasing counter held in the search context — a component + * bumps the counter to "send" a request and subscribers react without any + * global DOM lookups. + * + * Uses a layout effect so focus moves synchronously after a `flushSync` + * navigation (keeping the mobile keyboard open during the hero → nav morph). + * Wrap `effect` in `useCallback` so it only re-subscribes when its deps change. + */ +export function useSignalEffect(signal: number, effect: () => void): void { + const last = useRef(signal); + useLayoutEffect(() => { + if (signal === last.current) { + return; + } + last.current = signal; + effect(); + }, [signal, effect]); +} diff --git a/webui/src/hooks/use-signal.ts b/webui/src/hooks/use-signal.ts new file mode 100644 index 000000000..536821151 --- /dev/null +++ b/webui/src/hooks/use-signal.ts @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useCallback, useMemo, useState } from 'react'; + +export interface Signal { + /** Monotonically increasing counter; bumped on every emit. */ + signal: number; + /** Broadcast the signal to subscribers. */ + emit: () => void; +} + +/** + * Creates a one-off broadcast channel backed by a monotonically increasing + * counter. A component holding the signal calls `emit()` to fire it, and any + * number of subscribers react through `useSignalEffect` — coordinating + * imperative actions (e.g. moving focus) across components without global DOM + * lookups. Share the returned value through context to reach other components. + */ +export function useSignal(): Signal { + const [signal, setSignal] = useState(0); + const emit = useCallback(() => setSignal(n => n + 1), []); + return useMemo(() => ({ signal, emit }), [signal, emit]); +} diff --git a/webui/src/layout/app-layout.tsx b/webui/src/layout/app-layout.tsx index cb6ad17e0..203c61322 100644 --- a/webui/src/layout/app-layout.tsx +++ b/webui/src/layout/app-layout.tsx @@ -11,7 +11,8 @@ import { styled } from '@mui/material/styles'; import { Banner } from '../components/banner'; import { ShortcutsModal } from '../components/shortcuts-modal'; import { MainContext } from '../context'; -import { SearchSyncProvider } from '../search-sync-context'; +import { SearchProvider } from '../context/search/search-context'; +import { SearchFocusProvider } from '../context/search/search-focus-context'; import { KeyboardShortcutsProvider } from '../keyboard-shortcuts-context'; import { useShortcut } from '../use-shortcut'; import { getCookieValueByKey, setCookie } from '../utils'; @@ -135,9 +136,11 @@ const AppLayoutContent: FunctionComponent = props => { export const AppLayout: FunctionComponent = props => ( - - - + + + + + ); diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx index 2a61b4cd7..a1bc83ef6 100644 --- a/webui/src/layout/nav-search-field.tsx +++ b/webui/src/layout/nav-search-field.tsx @@ -4,62 +4,69 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, KeyboardEvent, useCallback, useEffect, useRef } from 'react'; +import { FunctionComponent, KeyboardEvent, useCallback, useLayoutEffect, useRef } from 'react'; import { Box } from '@mui/material'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { ExtensionSearchfield } from '../components/extension-searchfield'; import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; -import { useSearchSync } from '../search-sync-context'; +import { useSearch } from '../hooks/use-search'; +import { useSearchFocus } from '../hooks/use-search-focus'; +import { useSignalEffect } from '../hooks/use-signal-effect'; +import { useDebouncedCallback } from '../hooks/use-debounced-callback'; import { useShortcut } from '../use-shortcut'; export const NavSearchField: FunctionComponent = () => { const isHeroPage = useLocation().pathname === ExtensionListRoutes.MAIN; - const { navQuery, setNavQuery, searchHandler } = useSearchSync(); - const navigate = useNavigate(); + const { query, setQuery, search } = useSearch(); + const { focusSearchSignal, focusSearch, focusResults } = useSearchFocus(); + const inputRef = useRef(null); - // Keep a ref so handleNavSearch always calls the latest handler without needing - // to be recreated every time searchHandler changes (avoids stale-closure navigates) - const searchHandlerRef = useRef(searchHandler); - searchHandlerRef.current = searchHandler; + // Typing debounces navigation; Enter/clear search immediately. + const debouncedSearch = useDebouncedCallback(search); // On the hero (home) page the field is only a visual placeholder rendered at // opacity 0 for the view-transition morph. Mark the subtree `inert` so it is // removed from the tab order and the accessibility tree — otherwise keyboard - // users land on an invisible input. + // users land on an invisible input. This runs as a layout effect (before the + // focus signal effect below) so that when navigating away from the hero page + // the `inert` flag is cleared synchronously, before the focus request fires — + // otherwise focus would land on a still-inert element and be dropped. const fieldRef = useRef(null); - useEffect(() => { + useLayoutEffect(() => { if (fieldRef.current) { fieldRef.current.inert = isHeroPage; } }, [isHeroPage]); - const focusSearch = useCallback(() => { - const hero = document.getElementById('hero-search-input') as HTMLInputElement | null; - if (hero) { - hero.focus(); - // Move cursor to end so the user can continue typing after transitioning back - requestAnimationFrame(() => hero.setSelectionRange(hero.value.length, hero.value.length)); - return; - } - const nav = document.getElementById('search-input') as HTMLInputElement | null; - if (nav) { - nav.focus(); - requestAnimationFrame(() => nav.setSelectionRange(nav.value.length, nav.value.length)); - } - }, []); + // Take focus when requested — except on the hero page, where the hero search + // field owns focus instead. The onFocus handler moves the cursor to the end. + useSignalEffect( + focusSearchSignal, + useCallback(() => { + if (isHeroPage) { + return; + } + inputRef.current?.focus({ preventScroll: true }); + }, [isHeroPage]) + ); + // The '/' shortcut asks whichever search field is active to take focus. useShortcut({ key: '/', label: 'Focus search', order: 1, callback: focusSearch }); const handleNavSearch = useCallback( (q: string) => { - setNavQuery(q); - if (searchHandlerRef.current) { - searchHandlerRef.current(q); - } else { - navigate(`/search${q ? '?q=' + encodeURIComponent(q) : ''}`); - } + setQuery(q); + debouncedSearch({ query: q }); }, - [navigate, setNavQuery] + [setQuery, debouncedSearch] + ); + + const handleNavSubmit = useCallback( + (q: string) => { + debouncedSearch.cancel(); + search({ query: q }); + }, + [debouncedSearch, search] ); // Move cursor to end when the input gains focus (e.g. after view-transition morphs @@ -71,18 +78,20 @@ export const NavSearchField: FunctionComponent = () => { // ArrowDown moves focus from the search field into the results grid (first card), // where two-axis arrow navigation takes over. Escape blurs the field. - const handleInputKeyDown = useCallback((e: KeyboardEvent) => { - if (e.key === 'Escape') { - (e.target as HTMLInputElement).blur(); - return; - } - if (e.key !== 'ArrowDown') return; - const firstCard = document.querySelector('[data-grid-item], a[data-ext-card]') as HTMLElement | null; - if (firstCard) { + const handleInputKeyDown = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + (e.target as HTMLInputElement).blur(); + return; + } + if (e.key !== 'ArrowDown') { + return; + } e.preventDefault(); - firstCard.focus(); - } - }, []); + focusResults(); + }, + [focusResults] + ); return ( { '& > *': { mb: '0 !important' } }}> void; +} + +/** "Browse by category" section: a horizontal pill row on mobile, a card grid on desktop. */ +export const BrowseCategories: FunctionComponent = ({ categories, onSelect }) => { + if (categories.length === 0) { + return null; + } + return ( +
+ Browse by category + + {categories.map(cat => ( + onSelect(cat)} + /> + ))} + + + {categories.map(cat => ( + onSelect(cat)} + /> + ))} + +
+ ); +}; diff --git a/webui/src/pages/home/curated-sections.tsx b/webui/src/pages/home/curated-sections.tsx new file mode 100644 index 000000000..178147ccd --- /dev/null +++ b/webui/src/pages/home/curated-sections.tsx @@ -0,0 +1,88 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { Box, Typography } from '@mui/material'; +import { ExtensionCard } from '../../components/extension-card'; +import { Section } from '../../components/layout'; +import { CURATED_SIZE, CuratedRow } from './use-home-data'; + +interface CuratedSectionsProps { + rows: CuratedRow[]; + onSeeAll: () => void; +} + +/** Renders the curated extension rows (e.g. "Most downloaded"), skipping empty/loading ones. */ +export const CuratedSections: FunctionComponent = ({ rows, onSeeAll }) => ( + <> + {rows.map( + row => + !row.loading && + row.extensions.length > 0 && ( +
+ + + + {row.title} + + + {row.subtitle} + + + + See all → + + + + {row.extensions.map((ext, idx) => ( + + ))} + +
+ ) + )} + +); diff --git a/webui/src/pages/home/get-involved.tsx b/webui/src/pages/home/get-involved.tsx new file mode 100644 index 000000000..0b78884ae --- /dev/null +++ b/webui/src/pages/home/get-involved.tsx @@ -0,0 +1,90 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { FunctionComponent } from 'react'; +import { Box, Typography } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { HomeSettings } from '../../page-settings'; +import { Section, Eyebrow } from '../../components/layout'; + +const GetInvolvedCard = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + borderRadius: '16px', + padding: '24px', + display: 'flex', + flexDirection: 'column' +})); + +interface GetInvolvedProps { + involvement: HomeSettings['involvement']; +} + +/** Consumer-configured "Get Involved" cards (contribute, sponsor, etc.). */ +export const GetInvolved: FunctionComponent = ({ involvement }) => { + if (!involvement || involvement.cards.length === 0) { + return null; + } + return ( +
+ + {involvement.heading ?? 'Get Involved'} + + + {involvement.cards.map(card => ( + + + svg': { fontSize: 18 } + }}> + {card.icon} + + {card.title} + + + {card.description} + + + {card.label} + + + ))} + +
+ ); +}; diff --git a/webui/src/pages/home/hero-search.tsx b/webui/src/pages/home/hero-search.tsx new file mode 100644 index 000000000..5f89134e3 --- /dev/null +++ b/webui/src/pages/home/hero-search.tsx @@ -0,0 +1,250 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { ChangeEvent, ComponentType, FormEvent, FunctionComponent, useCallback, useRef, useState } from 'react'; +import { Box, ButtonBase, Container, Typography } from '@mui/material'; +import { flushSync } from 'react-dom'; +import { styled, alpha } from '@mui/material/styles'; +import { useSearch } from '../../hooks/use-search'; +import { useSearchFocus } from '../../hooks/use-search-focus'; +import { useSignalEffect } from '../../hooks/use-signal-effect'; +import { useDebouncedCallback } from '../../hooks/use-debounced-callback'; +import { MONO_FONT } from '../../default/theme'; + +const HeroSearchWrap = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: '13px', + backgroundColor: theme.palette.surface2, + border: `1px solid ${theme.palette.divider}`, + borderRadius: '15px', + height: '62px', + paddingLeft: '20px', + paddingRight: '8px', + [theme.breakpoints.down('sm')]: { + height: '54px', + paddingLeft: '14px', + gap: '10px' + }, + boxShadow: 'var(--shadow)', + transition: 'border-color 0.2s ease, box-shadow 0.3s ease', + '&:focus-within': { + borderColor: theme.palette.secondary.main, + boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}, 0 18px 70px -10px ${alpha(theme.palette.secondary.main, 0.45)}` + } +})); + +const HeroInput = styled('input')(({ theme }) => ({ + flex: 1, + height: '100%', + border: 'none', + outline: 'none', + background: 'none', + color: theme.palette.text.primary, + fontSize: '17px', + fontFamily: MONO_FONT, + '&::placeholder': { color: theme.palette.text.disabled } +})); + +const HeroSubmitButton = styled(ButtonBase)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: '8px', + height: '46px', + padding: '0 22px', + borderRadius: '11px', + overflow: 'hidden', + backgroundColor: theme.palette.secondary.main, + color: '#fff', + fontSize: '15px', + fontWeight: 600, + flexShrink: 0, + transition: 'background 0.14s', + [theme.breakpoints.down('sm')]: { + height: '40px', + padding: '0 14px', + borderRadius: '9px' + }, + '&:hover': { backgroundColor: theme.palette.secondary.dark } +})); + +const PopularChip = styled(ButtonBase)(({ theme }) => ({ + backgroundColor: theme.palette.surface2, + border: `1px solid ${theme.palette.divider}`, + color: theme.palette.text.secondary, + fontSize: '13px', + fontWeight: 500, + padding: '6px 13px', + borderRadius: '999px', + overflow: 'hidden', + fontFamily: MONO_FONT, + transition: 'border-color 0.14s, color 0.14s', + '&:hover': { + borderColor: theme.palette.secondary.main, + color: theme.palette.secondary.light + } +})); + +interface HeroSearchProps { + searchHeader?: ComponentType; + popularSearches: string[]; +} + +/** + * Owns a local copy of the hero field's `query` so keystrokes only re-render the + * field, not the whole homepage. Typing debounces `search`; submit and popular + * chips search immediately. + */ +export const HeroSearch: FunctionComponent = ({ searchHeader: SearchHeader, popularSearches }) => { + const { query: initialQuery, setQuery, search } = useSearch(); + const { focusSearchSignal, focusSearch } = useSearchFocus(); + const [query, setLocalQuery] = useState(() => initialQuery); + const heroInputRef = useRef(null); + + // Focus the hero input when focus is requested (e.g. the '/' shortcut on the home page). + useSignalEffect( + focusSearchSignal, + useCallback(() => { + const el = heroInputRef.current; + if (!el) { + return; + } + el.focus(); + // Move cursor to end so the user can keep typing. + requestAnimationFrame(() => el.setSelectionRange(el.value.length, el.value.length)); + }, []) + ); + + // Wrap search calls with a view transition so the hero input morphs into the nav bar. + type ViewTransitionDocument = Document & { + startViewTransition?: (callback: () => void) => { finished: Promise }; + }; + + const searchWithTransition = useCallback( + (q: string) => { + // Sync context immediately so the nav bar input value is ready before the + // navigation commits (the hero input morphs into the nav field mid-transition). + setQuery(q); + const shouldFocus = Boolean(q); + // flushSync inside the transition commits the navigation and the focus signal + // synchronously, so the nav field takes focus while the hero input is still + // focused — keeping the mobile keyboard open across the morph. + const go = () => { + flushSync(() => { + search({ query: q }); + if (shouldFocus) focusSearch(); + }); + }; + const doc = document as ViewTransitionDocument; + if (doc.startViewTransition) { + const transition = doc.startViewTransition(go); + // Re-issue the focus request once the transition settles as a fallback: + // the synchronous focus above can be interrupted by the morph, and by now + // the nav field is fully mounted and interactive. + if (shouldFocus) { + transition.finished.then(focusSearch).catch(() => undefined); + } + } else { + go(); + } + }, + [search, setQuery, focusSearch] + ); + + const debouncedSearch = useDebouncedCallback(searchWithTransition); + + const handleInputChange = (e: ChangeEvent) => { + const val = e.target.value; + setLocalQuery(val); + if (val.trim()) { + debouncedSearch(val.trim()); + } else { + // Clearing the field shouldn't navigate away from the home page. + debouncedSearch.cancel(); + } + }; + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + debouncedSearch.cancel(); + if (query.trim()) searchWithTransition(query.trim()); + }; + + return ( + + {SearchHeader && } + + + + / + + + + + + + + + search + + + + + {popularSearches.length > 0 && ( + + + Popular: + + {popularSearches.map(chip => ( + searchWithTransition(chip)} style={{ flexShrink: 0 }}> + {chip} + + ))} + + )} + + ); +}; diff --git a/webui/src/pages/home/home-page.tsx b/webui/src/pages/home/home-page.tsx index 4f0cbbc22..71a5631cc 100644 --- a/webui/src/pages/home/home-page.tsx +++ b/webui/src/pages/home/home-page.tsx @@ -4,58 +4,33 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useCallback } from 'react'; -import { flushSync } from 'react-dom'; -import { useNavigate } from 'react-router-dom'; -import { useSearchSync } from '../../search-sync-context'; -import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; -import { addQuery } from '../../utils'; -import { HomepageView } from './homepage-view'; - +import { FunctionComponent, useContext } from 'react'; +import { Box } from '@mui/material'; +import { MainContext } from '../../context'; +import { useSearch } from '../../hooks/use-search'; +import { HeroSearch } from './hero-search'; +import { BrowseCategories } from './browse-categories'; +import { CuratedSections } from './curated-sections'; +import { GetInvolved } from './get-involved'; +import { DEFAULT_CURATED_SECTIONS, useHomeData } from './use-home-data'; + +/** Landing page: hero search, category browser, curated extension rows and get-involved cards. */ export const HomePage: FunctionComponent = () => { - const { setNavQuery } = useSearchSync(); - const navigate = useNavigate(); - - const focusNavSearch = useCallback(() => { - const input = document.getElementById('search-input') as HTMLInputElement | null; - // preventScroll: a plain focus() scrolls the input into view, which on mobile - // fights the scroll-to-top and can leave the page sitting over the results. - input?.focus({ preventScroll: true }); - }, []); - - const handleSearch = useCallback( - (query: string, category?: string) => { - const cat = category ?? ''; - const queries: { key: string; value: string }[] = []; - if (query) queries.push({ key: 'q', value: query }); - if (cat) queries.push({ key: 'category', value: cat }); - const url = addQuery(ExtensionListRoutes.SEARCH, queries); - - const run = () => { - setNavQuery(query); - navigate(url, { state: { _q: query, _cat: cat } }); - }; - - // Only steal focus into the search bar for an actual text search — not when - // navigating to /search by browsing a category or "view all". - const shouldFocus = Boolean(query); - - if ('startViewTransition' in document) { - const transition = (document as any).startViewTransition(() => { - flushSync(run); - // Move focus to the nav search field synchronously, while the hero - // input is still focused, so the mobile keyboard stays open. Waiting - // for transition.finished leaves a gap that drops the keyboard. - if (shouldFocus) focusNavSearch(); - }); - if (shouldFocus) transition.finished.then(focusNavSearch); - } else { - flushSync(run); - if (shouldFocus) focusNavSearch(); - } - }, - [navigate, setNavQuery, focusNavSearch] + const { pageSettings } = useContext(MainContext); + const { search } = useSearch(); + const home = pageSettings.elements.home; + const curatedSections = home?.curatedSections ?? DEFAULT_CURATED_SECTIONS; + const { categories, rows } = useHomeData(curatedSections); + + return ( + + + search({ query: '', category })} /> + search({ query: '' })} /> + + ); - - return ; }; diff --git a/webui/src/pages/home/homepage-view.tsx b/webui/src/pages/home/homepage-view.tsx deleted file mode 100644 index 6d059c9bb..000000000 --- a/webui/src/pages/home/homepage-view.tsx +++ /dev/null @@ -1,448 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { ComponentType, FunctionComponent, useContext, useEffect, useRef, useState } from 'react'; -import { Box, ButtonBase, Typography } from '@mui/material'; -import { styled, alpha } from '@mui/material/styles'; -import { MainContext } from '../../context'; -import { useSearchSync } from '../../search-sync-context'; -import { ExtensionCard } from '../../components/extension-card'; -import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; -import { HomeCuratedSection } from '../../page-settings'; -import { MONO_FONT } from '../../default/theme'; -import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; -import { CategoryPill } from '../../components/category-pill'; -import { CategoryCard } from '../../components/category-card'; -import { Section, Eyebrow } from '../../components/layout'; - -const CURATED_SIZE = 6; -const EXCLUDED_CATEGORIES = new Set(['Other', 'SCM Providers', 'Extension Packs']); - -/** Curated rows shown when the consumer does not configure `home.curatedSections`. */ -const DEFAULT_CURATED_SECTIONS: HomeCuratedSection[] = [ - { title: 'Most downloaded', subtitle: 'The extensions developers rely on every day', sortBy: 'downloadCount' }, - { title: 'Recently updated', subtitle: 'Fresh releases from publishers this week', sortBy: 'timestamp' } -]; - -const HeroSearchWrap = styled(Box)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: '13px', - backgroundColor: theme.palette.surface2, - border: `1px solid ${theme.palette.divider}`, - borderRadius: '15px', - height: '62px', - paddingLeft: '20px', - paddingRight: '8px', - [theme.breakpoints.down('sm')]: { - height: '54px', - paddingLeft: '14px', - gap: '10px' - }, - boxShadow: 'var(--shadow)', - transition: 'border-color 0.2s ease, box-shadow 0.3s ease', - '&:focus-within': { - borderColor: theme.palette.secondary.main, - boxShadow: `0 0 0 3px ${alpha(theme.palette.secondary.main, 0.16)}, 0 18px 70px -10px ${alpha(theme.palette.secondary.main, 0.45)}` - } -})); - -const HeroInput = styled('input')(({ theme }) => ({ - flex: 1, - height: '100%', - border: 'none', - outline: 'none', - background: 'none', - color: theme.palette.text.primary, - fontSize: '17px', - fontFamily: MONO_FONT, - '&::placeholder': { color: theme.palette.text.disabled } -})); - -const HeroSubmitButton = styled(ButtonBase)(({ theme }) => ({ - display: 'flex', - alignItems: 'center', - gap: '8px', - height: '46px', - padding: '0 22px', - borderRadius: '11px', - overflow: 'hidden', - backgroundColor: theme.palette.secondary.main, - color: '#fff', - fontSize: '15px', - fontWeight: 600, - flexShrink: 0, - transition: 'background 0.14s', - [theme.breakpoints.down('sm')]: { - height: '40px', - padding: '0 14px', - borderRadius: '9px' - }, - '&:hover': { backgroundColor: theme.palette.secondary.dark } -})); - -const PopularChip = styled(ButtonBase)(({ theme }) => ({ - backgroundColor: theme.palette.surface2, - border: `1px solid ${theme.palette.divider}`, - color: theme.palette.text.secondary, - fontSize: '13px', - fontWeight: 500, - padding: '6px 13px', - borderRadius: '999px', - overflow: 'hidden', - fontFamily: MONO_FONT, - transition: 'border-color 0.14s, color 0.14s', - '&:hover': { - borderColor: theme.palette.secondary.main, - color: theme.palette.secondary.light - } -})); - -const GetInvolvedCard = styled(Box)(({ theme }) => ({ - backgroundColor: theme.palette.background.paper, - border: `1px solid ${theme.palette.divider}`, - borderRadius: '16px', - padding: '24px', - display: 'flex', - flexDirection: 'column' -})); - -interface CuratedRow extends HomeCuratedSection { - extensions: SearchEntry[]; - loading: boolean; -} - -interface HomepageViewProps { - onSearch: (query: string, category?: string) => void; -} - -interface HeroSearchProps { - onSearch: HomepageViewProps['onSearch']; - searchHeader?: ComponentType; - popularSearches: string[]; -} - -/** - * Owns the hero search field's `query` state so keystrokes only re-render the - * field, not the whole homepage (categories grid and curated rows). - */ -const HeroSearch: FunctionComponent = ({ onSearch, searchHeader: SearchHeader, popularSearches }) => { - const { navQuery } = useSearchSync(); - const [query, setQuery] = useState(() => navQuery); - const searchTimerRef = useRef(); - - const handleInputChange = (e: React.ChangeEvent) => { - const val = e.target.value; - setQuery(val); - clearTimeout(searchTimerRef.current); - if (val.trim()) { - searchTimerRef.current = window.setTimeout(() => onSearch(val.trim()), 250); - } - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - clearTimeout(searchTimerRef.current); - if (query.trim()) onSearch(query.trim()); - }; - - return ( - - {SearchHeader && } - - - - / - - - - - - - - - search - - - - - {popularSearches.length > 0 && ( - - - Popular: - - {popularSearches.map(chip => ( - onSearch(chip)} style={{ flexShrink: 0 }}> - {chip} - - ))} - - )} - - ); -}; - -export const HomepageView: FunctionComponent = ({ onSearch }) => { - const { service, pageSettings } = useContext(MainContext); - const SearchHeader = pageSettings.elements.searchHeader; - const home = pageSettings.elements.home; - const popularSearches = home?.popularSearches ?? []; - const curatedSections = home?.curatedSections ?? DEFAULT_CURATED_SECTIONS; - const involvement = home?.involvement; - - const [categories, setCategories] = useState([]); - const [rows, setRows] = useState(() => - curatedSections.map(section => ({ ...section, extensions: [], loading: true })) - ); - - useEffect(() => { - setCategories( - Array.from(service.getCategories()) - .filter(c => !EXCLUDED_CATEGORIES.has(c)) - .sort((a, b) => a.localeCompare(b)) - ); - - const abortController = new AbortController(); - curatedSections.forEach((section, idx) => { - service - .search(abortController, { - query: '', - category: '', - offset: 0, - size: CURATED_SIZE, - sortBy: section.sortBy, - sortOrder: 'desc' as SortOrder - }) - .then(result => { - if (isError(result)) { - return; - } - const { extensions } = result as SearchResult; - setRows(prev => prev.map((row, i) => (i === idx ? { ...row, extensions, loading: false } : row))); - }) - .catch(() => setRows(prev => prev.map((row, i) => (i === idx ? { ...row, loading: false } : row)))); - }); - return () => abortController.abort(); - }, [service, curatedSections]); - - return ( - - - - {categories.length > 0 && ( -
- Browse by category - - {categories.map(cat => ( - onSearch('', cat)} - /> - ))} - - - {categories.map(cat => ( - onSearch('', cat)} - /> - ))} - -
- )} - - {rows.map( - row => - !row.loading && - row.extensions.length > 0 && ( -
- - - - {row.title} - - - {row.subtitle} - - - onSearch('')} - sx={{ - background: 'none', - border: 'none', - color: 'secondary.light', - fontSize: '14px', - fontWeight: 600, - cursor: 'pointer' - }}> - See all → - - - - {row.extensions.map((ext, idx) => ( - - ))} - -
- ) - )} - - {involvement && involvement.cards.length > 0 && ( -
- - {involvement.heading ?? 'Get Involved'} - - - {involvement.cards.map(card => ( - - - svg': { fontSize: 18 } - }}> - {card.icon} - - {card.title} - - - {card.description} - - - {card.label} - - - ))} - -
- )} -
- ); -}; diff --git a/webui/src/pages/home/use-home-data.ts b/webui/src/pages/home/use-home-data.ts new file mode 100644 index 000000000..31c6f73ea --- /dev/null +++ b/webui/src/pages/home/use-home-data.ts @@ -0,0 +1,75 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useContext, useEffect, useState } from 'react'; +import { MainContext } from '../../context'; +import { ExtensionCategory, SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; +import { HomeCuratedSection } from '../../page-settings'; + +/** Number of extensions fetched for each curated row. */ +export const CURATED_SIZE = 6; + +/** Categories that add little value on the home page grid. */ +const EXCLUDED_CATEGORIES = new Set(['Other', 'SCM Providers', 'Extension Packs']); + +/** Curated rows shown when the consumer does not configure `home.curatedSections`. */ +export const DEFAULT_CURATED_SECTIONS: HomeCuratedSection[] = [ + { title: 'Most downloaded', subtitle: 'The extensions developers rely on every day', sortBy: 'downloadCount' }, + { title: 'Recently updated', subtitle: 'Fresh releases from publishers this week', sortBy: 'timestamp' } +]; + +export interface CuratedRow extends HomeCuratedSection { + extensions: SearchEntry[]; + loading: boolean; +} + +/** + * Loads the home page data: the browsable category list (excluding a few noisy + * ones) and the curated extension rows, each fetched with its configured + * ordering. Rows start in a loading state and fill in as requests resolve. + */ +export function useHomeData(curatedSections: HomeCuratedSection[]): { + categories: ExtensionCategory[]; + rows: CuratedRow[]; +} { + const { service } = useContext(MainContext); + const [categories, setCategories] = useState([]); + const [rows, setRows] = useState(() => + curatedSections.map(section => ({ ...section, extensions: [], loading: true })) + ); + + useEffect(() => { + setCategories( + Array.from(service.getCategories()) + .filter(c => !EXCLUDED_CATEGORIES.has(c)) + .sort((a, b) => a.localeCompare(b)) + ); + + const abortController = new AbortController(); + curatedSections.forEach((section, idx) => { + service + .search(abortController, { + query: '', + category: '', + offset: 0, + size: CURATED_SIZE, + sortBy: section.sortBy, + sortOrder: 'desc' as SortOrder + }) + .then(result => { + if (isError(result)) { + return; + } + const { extensions } = result as SearchResult; + setRows(prev => prev.map((row, i) => (i === idx ? { ...row, extensions, loading: false } : row))); + }) + .catch(() => setRows(prev => prev.map((row, i) => (i === idx ? { ...row, loading: false } : row)))); + }); + return () => abortController.abort(); + }, [service, curatedSections]); + + return { categories, rows }; +} diff --git a/webui/src/pages/search/search-page.tsx b/webui/src/pages/search/search-page.tsx index 03f3af1cb..eae5f02a5 100644 --- a/webui/src/pages/search/search-page.tsx +++ b/webui/src/pages/search/search-page.tsx @@ -4,31 +4,24 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { FunctionComponent, useLayoutEffect } from 'react'; -import { Box } from '@mui/material'; -import { ExtensionCategory } from '../../extension-registry-types'; +import { FunctionComponent, useLayoutEffect, useState } from 'react'; +import { Box, Container } from '@mui/material'; +import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; import { ExtensionList } from '../../components/extension-list'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; import { CategoryListItem } from '../../components/category-list-item'; import { Eyebrow } from '../../components/layout'; -import { useSearchFilter } from './use-search-filter'; +import { useSearch } from '../../hooks/use-search'; +import { useCategories } from './use-categories'; import { SearchHeader } from './search-header'; export const SearchPage: FunctionComponent = () => { - const { - searchQuery, - category, - sortBy, - sortOrder, - debounceTime, - resultNumber, - categories, - onResultCount, - onCategoryChanged, - onSortByChanged, - onSortOrderChanged - } = useSearchFilter(); + const { filter, search } = useSearch(); + const { query: searchQuery, category, sortBy, sortOrder } = filter; + const categories = useCategories(); + + const [resultNumber, setResultNumber] = useState(0); useLayoutEffect(() => { // Entering the results should always start at the top, not wherever the @@ -37,14 +30,11 @@ export const SearchPage: FunctionComponent = () => { }, []); return ( - + pb: { xs: '18px', sm: '30px' } + }} + maxWidth='xl'> {/* Mobile category pills — outside the flex row so negative-margin bleed isn't clipped */} {categories.length > 0 && ( { label={cat || 'All'} icon={Icon} isSelected={category === cat} - onClick={() => onCategoryChanged(cat)} + onClick={() => search({ category: cat })} /> ); })} @@ -103,7 +93,7 @@ export const SearchPage: FunctionComponent = () => { label={cat || 'All categories'} icon={Icon} isSelected={category === cat} - onClick={() => onCategoryChanged(cat)} + onClick={() => search({ category: cat })} /> ); })} @@ -117,16 +107,15 @@ export const SearchPage: FunctionComponent = () => { sortOrder={sortOrder} searchQuery={searchQuery} category={category} - onSortByChanged={onSortByChanged} - onSortOrderChanged={onSortOrderChanged} + onSortByChanged={(sortBy: SortBy) => search({ sortBy })} + onSortOrderChanged={(sortOrder: SortOrder) => search({ sortOrder })} /> -
+
); }; diff --git a/webui/src/pages/search/use-categories.ts b/webui/src/pages/search/use-categories.ts new file mode 100644 index 000000000..636393d98 --- /dev/null +++ b/webui/src/pages/search/use-categories.ts @@ -0,0 +1,27 @@ +/****************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation. + * + * SPDX-License-Identifier: EPL-2.0 + *****************************************************************************/ + +import { useContext, useEffect, useState } from 'react'; +import { MainContext } from '../../context'; +import { ExtensionCategory } from '../../extension-registry-types'; + +export function useCategories(): ExtensionCategory[] { + const context = useContext(MainContext); + const [categories, setCategories] = useState([]); + + useEffect(() => { + const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; + cats.sort((a, b) => { + if (a === b) return 0; + if (a === 'Other') return 1; + if (b === 'Other') return -1; + return a.localeCompare(b); + }); + setCategories(cats); + }, []); + + return categories; +} diff --git a/webui/src/pages/search/use-search-filter.ts b/webui/src/pages/search/use-search-filter.ts deleted file mode 100644 index 74e161b67..000000000 --- a/webui/src/pages/search/use-search-filter.ts +++ /dev/null @@ -1,132 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { useContext, useEffect, useLayoutEffect, useRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { MainContext } from '../../context'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; -import { useSearchSync } from '../../search-sync-context'; -import { addQuery } from '../../utils'; -import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; - -function buildSearchUrl(q: string, cat: ExtensionCategory | '', sb: SortBy, so: SortOrder): string { - const queries: { key: string; value: string }[] = []; - if (q) queries.push({ key: 'q', value: q }); - if (cat) queries.push({ key: 'category', value: cat }); - if (sb && sb !== 'relevance') queries.push({ key: 'sortBy', value: sb }); - if (so && so !== 'desc') queries.push({ key: 'sortOrder', value: so }); - return addQuery(ExtensionListRoutes.SEARCH, queries); -} - -export function useSearchFilter() { - const { search, state: locationState } = useLocation(); - const context = useContext(MainContext); - const { navQuery, setNavQuery, setSearchHandler } = useSearchSync(); - - // Navigation state is the most reliable fallback for values passed from HomePage - // (URL params may not yet be reflected in useLocation() during view transitions) - const navState = (locationState as { _q?: string; _cat?: string } | null) ?? {}; - - const [searchQuery, setSearchQuery] = useState(() => { - const fromUrl = new URLSearchParams(search).get('q') ?? ''; - return fromUrl || navQuery || navState._q || ''; - }); - const [category, setCategory] = useState(() => { - const fromUrl = (new URLSearchParams(search).get('category') as ExtensionCategory) ?? ''; - return fromUrl || (navState._cat as ExtensionCategory) || ''; - }); - const [sortBy, setSortBy] = useState( - () => (new URLSearchParams(search).get('sortBy') as SortBy) ?? 'relevance' - ); - const [sortOrder, setSortOrder] = useState( - () => (new URLSearchParams(search).get('sortOrder') as SortOrder) ?? 'desc' - ); - const [debounceTime, setDebounceTime] = useState(0); - const [resultNumber, setResultNumber] = useState(0); - const [categories, setCategories] = useState([]); - - // Kept current every render so the stable handler closure always dispatches to latest state - const handlerRef = useRef<(q: string) => void>(() => {}); - handlerRef.current = (q: string) => { - setSearchQuery(q); - setDebounceTime(1000); - history.replaceState(null, '', buildSearchUrl(q, category, sortBy, sortOrder)); - }; - - // Register this page as the nav bar's search handler; unregister on unmount - useLayoutEffect(() => { - const stable = (q: string) => handlerRef.current(q); - setSearchHandler(stable); - return () => setSearchHandler(null); - }, []); - - // On mount: if the URL has a query but navQuery is empty, push it to context so - // the navbar search bar reflects the current search - useLayoutEffect(() => { - if (!navQuery && searchQuery) setNavQuery(searchQuery); - }, []); - - // Keep local searchQuery in sync when the nav bar drives an external query change. - // Skip the initial mount: searchQuery is already seeded from the URL/navigation state, - // and the layout effect above pushes it into navQuery — reacting here on mount would - // wipe a URL-provided query (e.g. /search?q=vue) before navQuery has caught up. - const navSyncMounted = useRef(false); - useEffect(() => { - if (!navSyncMounted.current) { - navSyncMounted.current = true; - return; - } - if (navQuery === searchQuery) return; - setSearchQuery(navQuery); - setDebounceTime(500); - history.replaceState(null, '', buildSearchUrl(navQuery, category, sortBy, sortOrder)); - }, [navQuery]); - - // Load category list once on mount - useEffect(() => { - const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; - cats.sort((a, b) => { - if (a === b) return 0; - if (a === 'Other') return 1; - if (b === 'Other') return -1; - return a.localeCompare(b); - }); - setCategories(cats); - }, []); - - const updateURL = (q: string, cat: ExtensionCategory | '', sb?: SortBy, so?: SortOrder) => { - history.replaceState(null, '', buildSearchUrl(q, cat, sb ?? sortBy, so ?? sortOrder)); - }; - - const onCategoryChanged = (cat: ExtensionCategory | '') => { - setCategory(cat); - updateURL(searchQuery, cat); - }; - - const onSortByChanged = (sb: SortBy) => { - setSortBy(sb); - updateURL(searchQuery, category, sb, sortOrder); - }; - - const onSortOrderChanged = (so: SortOrder) => { - setSortOrder(so); - updateURL(searchQuery, category, sortBy, so); - }; - - return { - searchQuery, - category, - sortBy, - sortOrder, - debounceTime, - resultNumber, - categories, - onResultCount: setResultNumber, - onCategoryChanged, - onSortByChanged, - onSortOrderChanged - }; -} diff --git a/webui/src/search-sync-context.tsx b/webui/src/search-sync-context.tsx deleted file mode 100644 index d5dc69442..000000000 --- a/webui/src/search-sync-context.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/****************************************************************************** - * Copyright (c) 2026 Contributors to the Eclipse Foundation. - * - * SPDX-License-Identifier: EPL-2.0 - *****************************************************************************/ - -import { createContext, FunctionComponent, ReactNode, useCallback, useContext, useMemo, useState } from 'react'; - -interface SearchSyncContextValue { - navQuery: string; - setNavQuery: (q: string) => void; - searchHandler: ((q: string) => void) | null; - setSearchHandler: (fn: ((q: string) => void) | null) => void; -} - -const SearchSyncContext = createContext({ - navQuery: '', - setNavQuery: () => {}, - searchHandler: null, - setSearchHandler: () => {} -}); - -export const SearchSyncProvider: FunctionComponent<{ children: ReactNode }> = ({ children }) => { - const [navQuery, setNavQuery] = useState(''); - // useState treats any function passed directly as a reducer — wrap with () => fn to store functions correctly - const [searchHandler, setSearchHandlerState] = useState<((q: string) => void) | null>(null); - const setSearchHandler = useCallback((fn: ((q: string) => void) | null) => { - setSearchHandlerState(() => fn); - }, []); - const value = useMemo( - () => ({ navQuery, setNavQuery, searchHandler, setSearchHandler }), - [navQuery, searchHandler, setSearchHandler] - ); - return {children}; -}; - -// eslint-disable-next-line react-refresh/only-export-components -export const useSearchSync = () => useContext(SearchSyncContext); From 10800fe38b0b69e9866a8da6aee14c8ef1cdc21b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 12:32:44 +0200 Subject: [PATCH 06/81] fix: wrong negative margin --- webui/src/pages/home/hero-search.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webui/src/pages/home/hero-search.tsx b/webui/src/pages/home/hero-search.tsx index 5f89134e3..3cf3094f7 100644 --- a/webui/src/pages/home/hero-search.tsx +++ b/webui/src/pages/home/hero-search.tsx @@ -227,8 +227,8 @@ export const HeroSearch: FunctionComponent = ({ searchHeader: S flexWrap: { xs: 'nowrap', sm: 'wrap' }, overflowX: { xs: 'auto', sm: 'visible' }, mt: '18px', - mx: { xs: '-28px', sm: 0 }, - px: { xs: '28px', sm: 0 }, + mx: { xs: '-16px', sm: 0 }, + px: { xs: '16px', sm: 0 }, pb: { xs: '4px', sm: 0 }, '&::-webkit-scrollbar': { display: 'none' }, scrollbarWidth: 'none' From a4885aef2039db2357fe08af8460772533021ae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 13:52:19 +0200 Subject: [PATCH 07/81] feat: some UI consistency improvements --- webui/src/components/category-card.tsx | 7 ++-- webui/src/components/category-pill.tsx | 10 ++--- webui/src/components/extension-card.tsx | 2 +- webui/src/components/layout.tsx | 22 +++++++++-- webui/src/extension-registry-service.ts | 23 +----------- webui/src/extension-registry-types.ts | 17 --------- webui/src/hooks/use-search.ts | 3 +- webui/src/pages/home/browse-categories.tsx | 2 +- webui/src/pages/home/hero-search.tsx | 6 +-- webui/src/pages/home/use-home-data.ts | 27 ++++++++++---- webui/src/pages/search/search-header.tsx | 3 +- webui/src/pages/search/search-page.tsx | 3 +- webui/src/pages/search/use-categories.ts | 43 +++++++++++++--------- webui/src/pages/user/avatar.tsx | 12 ++---- webui/src/pages/user/logout.tsx | 14 +------ 15 files changed, 86 insertions(+), 108 deletions(-) diff --git a/webui/src/components/category-card.tsx b/webui/src/components/category-card.tsx index 080e3dd61..af030c735 100644 --- a/webui/src/components/category-card.tsx +++ b/webui/src/components/category-card.tsx @@ -21,7 +21,8 @@ const Root = styled(ButtonBase)(({ theme }) => ({ color: theme.palette.text.primary, width: '100%', transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', - '&:hover': cardHoverLift(theme) + ...cardHoverLift(theme), + '& .MuiTouchRipple-root': { color: theme.palette.secondary.main } })); export interface CategoryCardProps { @@ -37,8 +38,8 @@ export const CategoryCard: FunctionComponent = ({ label, icon width: '38px', height: '38px', borderRadius: '10px', - bgcolor: 'surface3', - color: 'text.secondary', + bgcolor: 'accentSoft', + color: 'secondary.main', display: 'flex', alignItems: 'center', justifyContent: 'center', diff --git a/webui/src/components/category-pill.tsx b/webui/src/components/category-pill.tsx index ac13fc674..a24fc0939 100644 --- a/webui/src/components/category-pill.tsx +++ b/webui/src/components/category-pill.tsx @@ -7,6 +7,7 @@ import { FunctionComponent } from 'react'; import { ButtonBase, SvgIconProps } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { accentHover } from './layout'; const Root = styled(ButtonBase, { shouldForwardProp: prop => prop !== 'isSelected' @@ -26,12 +27,7 @@ const Root = styled(ButtonBase, { whiteSpace: 'nowrap', fontFamily: 'inherit', transition: 'border-color 0.14s, color 0.14s', - '&:hover': isSelected - ? {} - : { - borderColor: theme.palette.secondary.main, - color: theme.palette.secondary.light - } + ...(isSelected ? {} : accentHover(theme)) })); export interface CategoryPillProps { @@ -43,7 +39,7 @@ export interface CategoryPillProps { export const CategoryPill: FunctionComponent = ({ label, icon: Icon, isSelected, onClick }) => ( - + {label} ); diff --git a/webui/src/components/extension-card.tsx b/webui/src/components/extension-card.tsx index b8988f63c..bcf5f6154 100644 --- a/webui/src/components/extension-card.tsx +++ b/webui/src/components/extension-card.tsx @@ -29,7 +29,7 @@ const CardRoot = styled(Paper)(({ theme }) => ({ minHeight: '206px', cursor: 'pointer', transition: 'border-color 0.15s, box-shadow 0.15s, transform 0.15s', - '&:hover': cardHoverLift(theme), + ...cardHoverLift(theme), // Keyboard focus ring mirrors the search field's :focus-within style. 'a:focus-visible &': { borderColor: theme.palette.secondary.main, diff --git a/webui/src/components/layout.tsx b/webui/src/components/layout.tsx index 60d0bf7fc..7e4e9c8df 100644 --- a/webui/src/components/layout.tsx +++ b/webui/src/components/layout.tsx @@ -42,9 +42,23 @@ export const cardSurface = (theme: Theme) => ({ borderRadius: theme.shape.borderRadiusCard }); -/** Hover treatment for interactive cards: accent border, shadow and lift. */ +/** Hover treatment for chips and pills: accent border and text color. Suppressed on touch devices. */ +export const accentHover = (theme: Theme) => ({ + '@media (hover: hover)': { + '&:hover': { + borderColor: theme.palette.secondary.main, + color: theme.palette.secondary.light + } + } +}); + +/** Hover treatment for interactive cards: accent border, shadow and lift. Suppressed on touch devices. */ export const cardHoverLift = (theme: Theme) => ({ - borderColor: theme.palette.secondary.main, - boxShadow: 'var(--shadow)', - transform: 'translateY(-2px)' + '@media (hover: hover)': { + '&:hover': { + borderColor: theme.palette.secondary.main, + boxShadow: 'var(--shadow)', + transform: 'translateY(-2px)' + } + } }); diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts index 00a0709b3..6eb560f93 100644 --- a/webui/src/extension-registry-service.ts +++ b/webui/src/extension-registry-service.ts @@ -11,7 +11,6 @@ import { Extension, UserData, - ExtensionCategory, ExtensionReviewList, PersonalAccessToken, SearchResult, @@ -204,26 +203,6 @@ export class ExtensionRegistryService { }); } - getCategories(): ExtensionCategory[] { - return [ - 'Programming Languages', - 'Snippets', - 'Linters', - 'Themes', - 'Debuggers', - 'Formatters', - 'Keymaps', - 'SCM Providers', - 'Other', - 'Extension Packs', - 'Language Packs', - 'Data Science', - 'Machine Learning', - 'Visualization', - 'Notebooks' - ]; - } - async getExtensionReviews( abortController: AbortController, extension: Extension @@ -1496,7 +1475,7 @@ export class AdminServiceImpl implements AdminService { export interface ExtensionFilter { query: string; - category: ExtensionCategory | ''; + category: string; size: number; offset: number; sortBy: SortBy; diff --git a/webui/src/extension-registry-types.ts b/webui/src/extension-registry-types.ts index f70b4bfcb..8e6f13a3e 100644 --- a/webui/src/extension-registry-types.ts +++ b/webui/src/extension-registry-types.ts @@ -201,23 +201,6 @@ export interface PersonalAccessToken { deleteTokenUrl: UrlString; } -export type ExtensionCategory = - | 'Programming Languages' - | 'Snippets' - | 'Linters' - | 'Themes' - | 'Debuggers' - | 'Formatters' - | 'Keymaps' - | 'SCM Providers' - | 'Other' - | 'Extension Packs' - | 'Language Packs' - | 'Data Science' - | 'Machine Learning' - | 'Visualization' - | 'Notebooks'; - export interface CsrfTokenJson { value: string; header: string; diff --git a/webui/src/hooks/use-search.ts b/webui/src/hooks/use-search.ts index dabceb7ec..c6b0b15dc 100644 --- a/webui/src/hooks/use-search.ts +++ b/webui/src/hooks/use-search.ts @@ -8,7 +8,8 @@ import { useCallback, useContext, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { SearchContext } from '../context/search/search-context'; import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; -import { ExtensionCategory, SortBy, SortOrder } from '../extension-registry-types'; +import { SortBy, SortOrder } from '../extension-registry-types'; +import { ExtensionCategory } from '../pages/search/use-categories'; import { addQuery } from '../utils'; export interface SearchFilter { diff --git a/webui/src/pages/home/browse-categories.tsx b/webui/src/pages/home/browse-categories.tsx index 4f66ed570..13689fab7 100644 --- a/webui/src/pages/home/browse-categories.tsx +++ b/webui/src/pages/home/browse-categories.tsx @@ -6,7 +6,7 @@ import { FunctionComponent } from 'react'; import { Box } from '@mui/material'; -import { ExtensionCategory } from '../../extension-registry-types'; +import { ExtensionCategory } from '../search/use-categories'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; import { CategoryCard } from '../../components/category-card'; diff --git a/webui/src/pages/home/hero-search.tsx b/webui/src/pages/home/hero-search.tsx index 3cf3094f7..09f95b8d5 100644 --- a/webui/src/pages/home/hero-search.tsx +++ b/webui/src/pages/home/hero-search.tsx @@ -8,6 +8,7 @@ import { ChangeEvent, ComponentType, FormEvent, FunctionComponent, useCallback, import { Box, ButtonBase, Container, Typography } from '@mui/material'; import { flushSync } from 'react-dom'; import { styled, alpha } from '@mui/material/styles'; +import { accentHover } from '../../components/layout'; import { useSearch } from '../../hooks/use-search'; import { useSearchFocus } from '../../hooks/use-search-focus'; import { useSignalEffect } from '../../hooks/use-signal-effect'; @@ -82,10 +83,7 @@ const PopularChip = styled(ButtonBase)(({ theme }) => ({ overflow: 'hidden', fontFamily: MONO_FONT, transition: 'border-color 0.14s, color 0.14s', - '&:hover': { - borderColor: theme.palette.secondary.main, - color: theme.palette.secondary.light - } + ...accentHover(theme) })); interface HeroSearchProps { diff --git a/webui/src/pages/home/use-home-data.ts b/webui/src/pages/home/use-home-data.ts index 31c6f73ea..e0454b881 100644 --- a/webui/src/pages/home/use-home-data.ts +++ b/webui/src/pages/home/use-home-data.ts @@ -6,14 +6,28 @@ import { useContext, useEffect, useState } from 'react'; import { MainContext } from '../../context'; -import { ExtensionCategory, SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; +import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; +import { ExtensionCategory, useCategories } from '../search/use-categories'; import { HomeCuratedSection } from '../../page-settings'; /** Number of extensions fetched for each curated row. */ export const CURATED_SIZE = 6; -/** Categories that add little value on the home page grid. */ -const EXCLUDED_CATEGORIES = new Set(['Other', 'SCM Providers', 'Extension Packs']); +/** Categories shown in the home page grid. */ +const HOME_CATEGORIES = new Set([ + 'AI', + 'Programming Languages', + 'Snippets', + 'Linters', + 'Themes', + 'Debuggers', + 'Formatters', + 'Keymaps', + 'Language Packs', + 'Data Science', + 'Machine Learning', + 'Notebooks' +]); /** Curated rows shown when the consumer does not configure `home.curatedSections`. */ export const DEFAULT_CURATED_SECTIONS: HomeCuratedSection[] = [ @@ -36,17 +50,14 @@ export function useHomeData(curatedSections: HomeCuratedSection[]): { rows: CuratedRow[]; } { const { service } = useContext(MainContext); + const allCategories = useCategories(); const [categories, setCategories] = useState([]); const [rows, setRows] = useState(() => curatedSections.map(section => ({ ...section, extensions: [], loading: true })) ); useEffect(() => { - setCategories( - Array.from(service.getCategories()) - .filter(c => !EXCLUDED_CATEGORIES.has(c)) - .sort((a, b) => a.localeCompare(b)) - ); + setCategories(allCategories.filter(c => HOME_CATEGORIES.has(c))); const abortController = new AbortController(); curatedSections.forEach((section, idx) => { diff --git a/webui/src/pages/search/search-header.tsx b/webui/src/pages/search/search-header.tsx index 4dcc08bd6..429ac8c86 100644 --- a/webui/src/pages/search/search-header.tsx +++ b/webui/src/pages/search/search-header.tsx @@ -6,7 +6,8 @@ import { FunctionComponent, KeyboardEvent } from 'react'; import { Box, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import { SortBy, SortOrder } from '../../extension-registry-types'; +import { ExtensionCategory } from './use-categories'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; diff --git a/webui/src/pages/search/search-page.tsx b/webui/src/pages/search/search-page.tsx index eae5f02a5..ba8429f40 100644 --- a/webui/src/pages/search/search-page.tsx +++ b/webui/src/pages/search/search-page.tsx @@ -6,7 +6,8 @@ import { FunctionComponent, useLayoutEffect, useState } from 'react'; import { Box, Container } from '@mui/material'; -import { ExtensionCategory, SortBy, SortOrder } from '../../extension-registry-types'; +import { SortBy, SortOrder } from '../../extension-registry-types'; +import { ExtensionCategory } from './use-categories'; import { ExtensionList } from '../../components/extension-list'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; diff --git a/webui/src/pages/search/use-categories.ts b/webui/src/pages/search/use-categories.ts index 636393d98..3e40179bf 100644 --- a/webui/src/pages/search/use-categories.ts +++ b/webui/src/pages/search/use-categories.ts @@ -4,24 +4,33 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { useContext, useEffect, useState } from 'react'; -import { MainContext } from '../../context'; -import { ExtensionCategory } from '../../extension-registry-types'; +export const CATEGORIES = [ + 'AI', + 'Programming Languages', + 'Snippets', + 'Linters', + 'Themes', + 'Debuggers', + 'Formatters', + 'Keymaps', + 'SCM Providers', + 'Other', + 'Extension Packs', + 'Language Packs', + 'Data Science', + 'Machine Learning', + 'Visualization', + 'Notebooks' +] as const; -export function useCategories(): ExtensionCategory[] { - const context = useContext(MainContext); - const [categories, setCategories] = useState([]); +export type ExtensionCategory = (typeof CATEGORIES)[number]; - useEffect(() => { - const cats = Array.from(context.service.getCategories()) as ExtensionCategory[]; - cats.sort((a, b) => { - if (a === b) return 0; - if (a === 'Other') return 1; - if (b === 'Other') return -1; - return a.localeCompare(b); - }); - setCategories(cats); - }, []); +const SORTED_CATEGORIES: ExtensionCategory[] = [...CATEGORIES].sort((a, b) => { + if (a === 'Other') return 1; + if (b === 'Other') return -1; + return a.localeCompare(b); +}); - return categories; +export function useCategories(): ExtensionCategory[] { + return SORTED_CATEGORIES; } diff --git a/webui/src/pages/user/avatar.tsx b/webui/src/pages/user/avatar.tsx index 4ed1eccf7..a2845a296 100644 --- a/webui/src/pages/user/avatar.tsx +++ b/webui/src/pages/user/avatar.tsx @@ -9,7 +9,7 @@ ********************************************************************************/ import { FunctionComponent, useContext, useRef, useState } from 'react'; -import { Avatar, Box, Divider, IconButton, Link, Menu, MenuItem, Typography } from '@mui/material'; +import { Avatar, Box, IconButton, Link, Menu, MenuItem, Typography } from '@mui/material'; import { Link as RouteLink } from 'react-router-dom'; import PersonIcon from '@mui/icons-material/Person'; import SettingsIcon from '@mui/icons-material/Settings'; @@ -117,7 +117,6 @@ export const UserAvatar: FunctionComponent = () => {
- { Admin Dashboard )} - logoutFormRef.current?.submit()} sx={menuItemSx}> - - - - Log out - - + + Log out diff --git a/webui/src/pages/user/logout.tsx b/webui/src/pages/user/logout.tsx index 11cffa8c5..dfca0ff81 100644 --- a/webui/src/pages/user/logout.tsx +++ b/webui/src/pages/user/logout.tsx @@ -9,19 +9,9 @@ * ****************************************************************************** */ import { PropsWithChildren, useContext, useEffect, useRef, useState, forwardRef } from 'react'; -import { Button } from '@mui/material'; -import { styled } from '@mui/material/styles'; import { isError, CsrfTokenJson } from '../../extension-registry-types'; import { MainContext } from '../../context'; -const LogoutButton = styled(Button)({ - cursor: 'pointer', - textDecoration: 'none', - border: 'none', - background: 'none', - padding: 0 -}); - export const LogoutForm = forwardRef(({ children }, ref) => { const [csrf, setCsrf] = useState(); const context = useContext(MainContext); @@ -45,9 +35,9 @@ export const LogoutForm = forwardRef(({ chil }; return ( -
+ {csrf ? : null} - {children} + {children}
); }); From 13a68ee6c1b7fbc882e5addfd289beea7d179c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:30:16 +0200 Subject: [PATCH 08/81] fix: cancel pending debounced search on unmount and route change --- webui/src/hooks/use-debounced-callback.ts | 14 +++++++++----- webui/src/layout/nav-search-field.tsx | 7 +++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/webui/src/hooks/use-debounced-callback.ts b/webui/src/hooks/use-debounced-callback.ts index b070301dc..5b55a3efd 100644 --- a/webui/src/hooks/use-debounced-callback.ts +++ b/webui/src/hooks/use-debounced-callback.ts @@ -11,7 +11,7 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { useMemo, useRef } from 'react'; +import { useEffect, useMemo, useRef } from 'react'; const DEFAULT_DELAY_MS = 300; @@ -33,10 +33,10 @@ export function useDebouncedCallback void>( const callbackRef = useRef(callback); callbackRef.current = callback; - return useMemo(() => { + const debounced = useMemo(() => { let timer: ReturnType | undefined; - const debounced = ((...args: Parameters) => { + const fn = ((...args: Parameters) => { clearTimeout(timer); timer = setTimeout(() => { timer = undefined; @@ -44,11 +44,15 @@ export function useDebouncedCallback void>( }, delay); }) as DebouncedCallback; - debounced.cancel = () => { + fn.cancel = () => { clearTimeout(timer); timer = undefined; }; - return debounced; + return fn; }, [delay]); + + useEffect(() => debounced.cancel, [debounced]); + + return debounced; } diff --git a/webui/src/layout/nav-search-field.tsx b/webui/src/layout/nav-search-field.tsx index a1bc83ef6..b37577099 100644 --- a/webui/src/layout/nav-search-field.tsx +++ b/webui/src/layout/nav-search-field.tsx @@ -16,13 +16,16 @@ import { useDebouncedCallback } from '../hooks/use-debounced-callback'; import { useShortcut } from '../use-shortcut'; export const NavSearchField: FunctionComponent = () => { - const isHeroPage = useLocation().pathname === ExtensionListRoutes.MAIN; + const { pathname } = useLocation(); + const isHeroPage = pathname === ExtensionListRoutes.MAIN; const { query, setQuery, search } = useSearch(); const { focusSearchSignal, focusSearch, focusResults } = useSearchFocus(); const inputRef = useRef(null); - // Typing debounces navigation; Enter/clear search immediately. + // Typing debounces navigation; Enter searches immediately. A route change + // drops any pending navigation (e.g. the user clicked a result mid-debounce). const debouncedSearch = useDebouncedCallback(search); + useLayoutEffect(() => debouncedSearch.cancel, [pathname, debouncedSearch]); // On the hero (home) page the field is only a visual placeholder rendered at // opacity 0 for the view-transition morph. Mark the subtree `inert` so it is From 1661c3070acfb7c7b39c928f47266b9046a2c6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:31:19 +0200 Subject: [PATCH 09/81] fix: redirect legacy search URLs to the new search route --- webui/src/pages/home/home-page.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/webui/src/pages/home/home-page.tsx b/webui/src/pages/home/home-page.tsx index 71a5631cc..e691486fa 100644 --- a/webui/src/pages/home/home-page.tsx +++ b/webui/src/pages/home/home-page.tsx @@ -6,16 +6,34 @@ import { FunctionComponent, useContext } from 'react'; import { Box } from '@mui/material'; +import { Navigate, useSearchParams } from 'react-router-dom'; import { MainContext } from '../../context'; import { useSearch } from '../../hooks/use-search'; +import { ExtensionListRoutes } from '../extension-list/extension-list-routes'; +import { addQuery } from '../../utils'; import { HeroSearch } from './hero-search'; import { BrowseCategories } from './browse-categories'; import { CuratedSections } from './curated-sections'; import { GetInvolved } from './get-involved'; import { DEFAULT_CURATED_SECTIONS, useHomeData } from './use-home-data'; -/** Landing page: hero search, category browser, curated extension rows and get-involved cards. */ +/** Landing page. Pre-redesign search URLs lived here (/?search=...), so redirect those to /search. */ export const HomePage: FunctionComponent = () => { + const [params] = useSearchParams(); + if (['search', 'category', 'sortBy', 'sortOrder'].some(key => params.has(key))) { + const target = addQuery(ExtensionListRoutes.SEARCH, [ + { key: 'q', value: params.get('search') ?? undefined }, + { key: 'category', value: params.get('category') ?? undefined }, + { key: 'sortBy', value: params.get('sortBy') ?? undefined }, + { key: 'sortOrder', value: params.get('sortOrder') ?? undefined } + ]); + return ; + } + return ; +}; + +/** Hero search, category browser, curated extension rows and get-involved cards. */ +const HomeContent: FunctionComponent = () => { const { pageSettings } = useContext(MainContext); const { search } = useSearch(); const home = pageSettings.elements.home; From 6c761c7ab73dabe868c45a76a8acee93edd872ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:33:44 +0200 Subject: [PATCH 10/81] fix: restore ExtensionCategory and getCategories in the public API --- webui/src/extension-registry-service.ts | 7 +++++++ webui/src/extension-registry-types.ts | 21 +++++++++++++++++++++ webui/src/hooks/use-search.ts | 3 +-- webui/src/pages/home/browse-categories.tsx | 2 +- webui/src/pages/home/use-home-data.ts | 3 ++- webui/src/pages/search/search-header.tsx | 2 +- webui/src/pages/search/search-page.tsx | 2 +- webui/src/pages/search/use-categories.ts | 21 +-------------------- 8 files changed, 35 insertions(+), 26 deletions(-) diff --git a/webui/src/extension-registry-service.ts b/webui/src/extension-registry-service.ts index 6eb560f93..a0b88b6b1 100644 --- a/webui/src/extension-registry-service.ts +++ b/webui/src/extension-registry-service.ts @@ -9,7 +9,9 @@ ********************************************************************************/ import { + CATEGORIES, Extension, + ExtensionCategory, UserData, ExtensionReviewList, PersonalAccessToken, @@ -203,6 +205,11 @@ export class ExtensionRegistryService { }); } + /** @deprecated Use the CATEGORIES constant from extension-registry-types instead. */ + getCategories(): ExtensionCategory[] { + return [...CATEGORIES]; + } + async getExtensionReviews( abortController: AbortController, extension: Extension diff --git a/webui/src/extension-registry-types.ts b/webui/src/extension-registry-types.ts index 8e6f13a3e..007379ab9 100644 --- a/webui/src/extension-registry-types.ts +++ b/webui/src/extension-registry-types.ts @@ -201,6 +201,27 @@ export interface PersonalAccessToken { deleteTokenUrl: UrlString; } +export const CATEGORIES = [ + 'AI', + 'Programming Languages', + 'Snippets', + 'Linters', + 'Themes', + 'Debuggers', + 'Formatters', + 'Keymaps', + 'SCM Providers', + 'Other', + 'Extension Packs', + 'Language Packs', + 'Data Science', + 'Machine Learning', + 'Visualization', + 'Notebooks' +] as const; + +export type ExtensionCategory = (typeof CATEGORIES)[number]; + export interface CsrfTokenJson { value: string; header: string; diff --git a/webui/src/hooks/use-search.ts b/webui/src/hooks/use-search.ts index c6b0b15dc..dabceb7ec 100644 --- a/webui/src/hooks/use-search.ts +++ b/webui/src/hooks/use-search.ts @@ -8,8 +8,7 @@ import { useCallback, useContext, useEffect } from 'react'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { SearchContext } from '../context/search/search-context'; import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; -import { SortBy, SortOrder } from '../extension-registry-types'; -import { ExtensionCategory } from '../pages/search/use-categories'; +import { ExtensionCategory, SortBy, SortOrder } from '../extension-registry-types'; import { addQuery } from '../utils'; export interface SearchFilter { diff --git a/webui/src/pages/home/browse-categories.tsx b/webui/src/pages/home/browse-categories.tsx index 13689fab7..4f66ed570 100644 --- a/webui/src/pages/home/browse-categories.tsx +++ b/webui/src/pages/home/browse-categories.tsx @@ -6,7 +6,7 @@ import { FunctionComponent } from 'react'; import { Box } from '@mui/material'; -import { ExtensionCategory } from '../search/use-categories'; +import { ExtensionCategory } from '../../extension-registry-types'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; import { CategoryCard } from '../../components/category-card'; diff --git a/webui/src/pages/home/use-home-data.ts b/webui/src/pages/home/use-home-data.ts index e0454b881..f0e21d83e 100644 --- a/webui/src/pages/home/use-home-data.ts +++ b/webui/src/pages/home/use-home-data.ts @@ -7,7 +7,8 @@ import { useContext, useEffect, useState } from 'react'; import { MainContext } from '../../context'; import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; -import { ExtensionCategory, useCategories } from '../search/use-categories'; +import { ExtensionCategory } from '../../extension-registry-types'; +import { useCategories } from '../search/use-categories'; import { HomeCuratedSection } from '../../page-settings'; /** Number of extensions fetched for each curated row. */ diff --git a/webui/src/pages/search/search-header.tsx b/webui/src/pages/search/search-header.tsx index 429ac8c86..5daaf0e62 100644 --- a/webui/src/pages/search/search-header.tsx +++ b/webui/src/pages/search/search-header.tsx @@ -7,7 +7,7 @@ import { FunctionComponent, KeyboardEvent } from 'react'; import { Box, Select, MenuItem, Typography, SelectChangeEvent } from '@mui/material'; import { SortBy, SortOrder } from '../../extension-registry-types'; -import { ExtensionCategory } from './use-categories'; +import { ExtensionCategory } from '../../extension-registry-types'; import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; diff --git a/webui/src/pages/search/search-page.tsx b/webui/src/pages/search/search-page.tsx index ba8429f40..8b35e1cfe 100644 --- a/webui/src/pages/search/search-page.tsx +++ b/webui/src/pages/search/search-page.tsx @@ -7,7 +7,7 @@ import { FunctionComponent, useLayoutEffect, useState } from 'react'; import { Box, Container } from '@mui/material'; import { SortBy, SortOrder } from '../../extension-registry-types'; -import { ExtensionCategory } from './use-categories'; +import { ExtensionCategory } from '../../extension-registry-types'; import { ExtensionList } from '../../components/extension-list'; import { CATEGORY_ICONS, DefaultCategoryIcon } from '../../components/categories'; import { CategoryPill } from '../../components/category-pill'; diff --git a/webui/src/pages/search/use-categories.ts b/webui/src/pages/search/use-categories.ts index 3e40179bf..d81797b28 100644 --- a/webui/src/pages/search/use-categories.ts +++ b/webui/src/pages/search/use-categories.ts @@ -4,26 +4,7 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -export const CATEGORIES = [ - 'AI', - 'Programming Languages', - 'Snippets', - 'Linters', - 'Themes', - 'Debuggers', - 'Formatters', - 'Keymaps', - 'SCM Providers', - 'Other', - 'Extension Packs', - 'Language Packs', - 'Data Science', - 'Machine Learning', - 'Visualization', - 'Notebooks' -] as const; - -export type ExtensionCategory = (typeof CATEGORIES)[number]; +import { CATEGORIES, ExtensionCategory } from '../../extension-registry-types'; const SORTED_CATEGORIES: ExtensionCategory[] = [...CATEGORIES].sort((a, b) => { if (a === 'Other') return 1; From c40011c30214f6ca7091976871e06a2ef35a114d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:34:59 +0200 Subject: [PATCH 11/81] fix: fetch home page rows with TanStack Query --- webui/src/pages/home/use-home-data.ts | 52 ++++++++++++++------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/webui/src/pages/home/use-home-data.ts b/webui/src/pages/home/use-home-data.ts index f0e21d83e..142f6bc9a 100644 --- a/webui/src/pages/home/use-home-data.ts +++ b/webui/src/pages/home/use-home-data.ts @@ -4,12 +4,14 @@ * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ -import { useContext, useEffect, useState } from 'react'; +import { useContext, useMemo } from 'react'; +import { useQueries } from '@tanstack/react-query'; import { MainContext } from '../../context'; import { SearchEntry, SearchResult, SortOrder, isError } from '../../extension-registry-types'; import { ExtensionCategory } from '../../extension-registry-types'; import { useCategories } from '../search/use-categories'; import { HomeCuratedSection } from '../../page-settings'; +import { controllerFromSignal } from '../../query-client'; /** Number of extensions fetched for each curated row. */ export const CURATED_SIZE = 6; @@ -44,7 +46,8 @@ export interface CuratedRow extends HomeCuratedSection { /** * Loads the home page data: the browsable category list (excluding a few noisy * ones) and the curated extension rows, each fetched with its configured - * ordering. Rows start in a loading state and fill in as requests resolve. + * ordering. Rows start in a loading state and fill in as requests resolve; + * failed rows end up empty and are hidden by the consumer. */ export function useHomeData(curatedSections: HomeCuratedSection[]): { categories: ExtensionCategory[]; @@ -52,36 +55,35 @@ export function useHomeData(curatedSections: HomeCuratedSection[]): { } { const { service } = useContext(MainContext); const allCategories = useCategories(); - const [categories, setCategories] = useState([]); - const [rows, setRows] = useState(() => - curatedSections.map(section => ({ ...section, extensions: [], loading: true })) - ); + const categories = useMemo(() => allCategories.filter(c => HOME_CATEGORIES.has(c)), [allCategories]); - useEffect(() => { - setCategories(allCategories.filter(c => HOME_CATEGORIES.has(c))); - - const abortController = new AbortController(); - curatedSections.forEach((section, idx) => { - service - .search(abortController, { + const results = useQueries({ + queries: curatedSections.map(section => ({ + queryKey: ['home-curated', section.sortBy], + queryFn: async ({ signal }: { signal: AbortSignal }): Promise => { + const result = await service.search(controllerFromSignal(signal), { query: '', category: '', offset: 0, size: CURATED_SIZE, sortBy: section.sortBy, sortOrder: 'desc' as SortOrder - }) - .then(result => { - if (isError(result)) { - return; - } - const { extensions } = result as SearchResult; - setRows(prev => prev.map((row, i) => (i === idx ? { ...row, extensions, loading: false } : row))); - }) - .catch(() => setRows(prev => prev.map((row, i) => (i === idx ? { ...row, loading: false } : row)))); - }); - return () => abortController.abort(); - }, [service, curatedSections]); + }); + if (isError(result)) { + throw result; + } + return (result as SearchResult).extensions; + }, + // service.search already retries transient failures internally. + retry: false + })) + }); + + const rows: CuratedRow[] = curatedSections.map((section, idx) => ({ + ...section, + extensions: results[idx].data ?? [], + loading: results[idx].isLoading + })); return { categories, rows }; } From 7694b7d19f6fdcce6539eaaada65f9fdcef921d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:35:48 +0200 Subject: [PATCH 12/81] fix: use router location in useSearch and push history when entering search --- webui/src/hooks/use-search.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/webui/src/hooks/use-search.ts b/webui/src/hooks/use-search.ts index dabceb7ec..24c9e3924 100644 --- a/webui/src/hooks/use-search.ts +++ b/webui/src/hooks/use-search.ts @@ -5,11 +5,10 @@ *****************************************************************************/ import { useCallback, useContext, useEffect } from 'react'; -import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'; import { SearchContext } from '../context/search/search-context'; import { ExtensionListRoutes } from '../pages/extension-list/extension-list-routes'; import { ExtensionCategory, SortBy, SortOrder } from '../extension-registry-types'; -import { addQuery } from '../utils'; export interface SearchFilter { query: string; @@ -30,6 +29,7 @@ export function filterToParams({ query, category, sortBy, sortOrder }: SearchFil export function useSearch() { const navigate = useNavigate(); + const { pathname } = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); const { query, setQuery } = useContext(SearchContext); @@ -46,27 +46,20 @@ export function useSearch() { }, [filter.query, setQuery]); // On the search page: patch URL params in place (replace, no new history entry). - // From anywhere else: navigate to the search route. + // From anywhere else: push a navigation to the search route. const search = useCallback( (patch: Partial) => { const next = { ...filter, ...patch }; + const params = filterToParams(next); - const { pathname } = window.location; if (pathname === ExtensionListRoutes.SEARCH) { - setSearchParams(filterToParams(next), { replace: true }); + setSearchParams(params, { replace: true }); return; } - const fromHome = pathname === ExtensionListRoutes.MAIN; - const url = addQuery(ExtensionListRoutes.SEARCH, [ - { key: 'q', value: next.query || undefined }, - { key: 'category', value: next.category || undefined }, - { key: 'sortBy', value: next.sortBy !== 'relevance' ? next.sortBy : undefined }, - { key: 'sortOrder', value: next.sortOrder !== 'desc' ? next.sortOrder : undefined } - ]); - navigate(url, { replace: !fromHome }); + navigate({ pathname: ExtensionListRoutes.SEARCH, search: new URLSearchParams(params).toString() }); }, - [navigate, filter, setSearchParams] + [navigate, pathname, filter, setSearchParams] ); return { query, setQuery, search, filter }; From 0fa5b603ce5e28a47225195a44ed70f8dbbfff83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:36:25 +0200 Subject: [PATCH 13/81] fix: ignore global shortcuts while a popup or dialog has focus --- webui/src/keyboard-shortcuts-context.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/webui/src/keyboard-shortcuts-context.tsx b/webui/src/keyboard-shortcuts-context.tsx index a82628943..440a23a0d 100644 --- a/webui/src/keyboard-shortcuts-context.tsx +++ b/webui/src/keyboard-shortcuts-context.tsx @@ -89,10 +89,16 @@ export const KeyboardShortcutsProvider: FunctionComponent = ( dirty.clear(); }; - const isInput = (target: EventTarget | null) => { + // Ignore keys typed into inputs and keys pressed while a popup owns focus + // (MUI selects/menus/dialogs render as listbox/menu/dialog roles, not native elements). + const shouldIgnore = (target: EventTarget | null) => { const el = target as HTMLElement; return ( - el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT' || el.isContentEditable + el.tagName === 'INPUT' || + el.tagName === 'TEXTAREA' || + el.tagName === 'SELECT' || + el.isContentEditable || + Boolean(el.closest?.('[role="listbox"], [role="menu"], [role="dialog"]')) ); }; @@ -101,7 +107,7 @@ export const KeyboardShortcutsProvider: FunctionComponent = ( if (pressed.size > 0) { pressed.forEach((_, code) => dirty.add(code)); dirty.add(e.code); - } else if (!isInput(e.target) && registryRef.current.has(e.key)) { + } else if (!shouldIgnore(e.target) && registryRef.current.has(e.key)) { e.preventDefault(); } pressed.set(e.code, e.key); @@ -116,7 +122,7 @@ export const KeyboardShortcutsProvider: FunctionComponent = ( const key = pressed.get(e.code) ?? e.key; pressed.delete(e.code); dirty.delete(e.code); - if (isDirty || isInput(e.target)) return; + if (isDirty || shouldIgnore(e.target)) return; const entry = registryRef.current.get(key); if (entry) entry.callback(); }; From 420fd28f5ff1297cf2240e4734865eec58ac3694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:37:29 +0200 Subject: [PATCH 14/81] fix: remove redundant debounce from extension list fetches --- webui/src/components/extension-list.tsx | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/webui/src/components/extension-list.tsx b/webui/src/components/extension-list.tsx index 1c18a2b4f..bfcfcad92 100644 --- a/webui/src/components/extension-list.tsx +++ b/webui/src/components/extension-list.tsx @@ -10,14 +10,12 @@ import { Box, CircularProgress } from '@mui/material'; import { ExtensionCard } from './extension-card'; import { isError, SearchEntry, SearchResult } from '../extension-registry-types'; import { ExtensionFilter } from '../extension-registry-service'; -import { debounce } from '../utils'; import { DelayedLoadIndicator } from './delayed-load-indicator'; import { useGridKeyboardNavigation } from './use-grid-keyboard-navigation'; import { MainContext } from '../context'; export const ExtensionList: FunctionComponent = props => { const abortController = useRef(new AbortController()); - const cancellationToken = useRef<{ timeout?: number }>({}); const enableLoadMore = useRef(false); const lastRequestedPage = useRef(0); const pageOffset = useRef(0); @@ -34,19 +32,24 @@ export const ExtensionList: FunctionComponent = props => { enableLoadMore.current = true; return () => { abortController.current.abort(); - clearTimeout(cancellationToken.current.timeout); enableLoadMore.current = false; }; }, []); useEffect(() => { filterSize.current = props.filter.size ?? filterSize.current; - debounce(async () => { + // Inputs are already debounced upstream; fetch immediately and drop + // responses that arrive after the filter changed again. + let stale = false; + (async () => { try { const result = await context.service.search(abortController.current, props.filter); if (isError(result)) { throw result; } + if (stale) { + return; + } const searchResult = result as SearchResult; props.onUpdate(searchResult.totalSize); @@ -62,11 +65,18 @@ export const ExtensionList: FunctionComponent = props => { setAppliedFilter(props.filter); setHasMore(actualSize < searchResult.totalSize && actualSize > 0); } catch (err) { - context.handleError(err); + if (!stale) { + context.handleError(err); + } } finally { - setLoading(false); + if (!stale) { + setLoading(false); + } } - }, cancellationToken.current); + })(); + return () => { + stale = true; + }; }, [props.filter.category, props.filter.query, props.filter.sortBy, props.filter.sortOrder]); const loadMore = async (p: number): Promise => { From d8e6773d2f2109c8f348908a687a1b19dde08cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 17:41:54 +0200 Subject: [PATCH 15/81] refactor: consolidate floating surface styling in the theme --- .../src/components/extension-searchfield.tsx | 6 +- webui/src/components/shortcuts-modal.tsx | 18 +---- webui/src/default/theme.tsx | 74 +++++++++---------- webui/src/layout/app-navbar.tsx | 10 +-- webui/src/layout/nav-search-field.tsx | 3 +- webui/src/pages/home/hero-search.tsx | 2 +- webui/src/pages/search/search-page.tsx | 5 +- 7 files changed, 48 insertions(+), 70 deletions(-) diff --git a/webui/src/components/extension-searchfield.tsx b/webui/src/components/extension-searchfield.tsx index 95348dc8f..e02689b24 100644 --- a/webui/src/components/extension-searchfield.tsx +++ b/webui/src/components/extension-searchfield.tsx @@ -30,10 +30,6 @@ const SearchWrap = styled(Box, { display: 'flex', alignItems: 'center', gap: '10px', - marginRight: 0, - marginBottom: 0, - [theme.breakpoints.up('md')]: { marginRight: theme.spacing(1), marginBottom: 0 }, - [theme.breakpoints.down('sm')]: { marginBottom: theme.spacing(2) }, border: hasError ? '2px solid' : '1px solid', borderColor: hasError ? theme.palette.error.main : theme.palette.divider, borderRadius: '11px', @@ -143,7 +139,7 @@ export const ExtensionSearchfield = forwardRef( onClick={handleSearchButtonClick} sx={{ bgcolor: 'secondary.main', - color: '#fff', + color: 'secondary.contrastText', borderRadius: '8px', p: '8px', flexShrink: 0, diff --git a/webui/src/components/shortcuts-modal.tsx b/webui/src/components/shortcuts-modal.tsx index 45a698b8e..6f0204e54 100644 --- a/webui/src/components/shortcuts-modal.tsx +++ b/webui/src/components/shortcuts-modal.tsx @@ -19,21 +19,7 @@ export const ShortcutsModal: FunctionComponent = ({ open, o const { shortcuts } = useKeyboardShortcuts(); return ( - + = ({ open, o - + {shortcuts.map((s, i) => ( ({ + border: `1px solid ${theme.palette.divider}`, + boxShadow: 'var(--shadow-lg)', + backgroundColor: theme.palette.background.paper, + backgroundImage: 'none', + '&.MuiPaper-rounded': { borderRadius: theme.shape.borderRadiusCard } +}); type Color = CSSProperties['color']; @@ -97,6 +108,9 @@ export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { borderRadius: 9, borderRadiusCard: 14 }, + mixins: { + toolbar: { minHeight: NAVBAR_HEIGHT } + }, palette: { mode: themeType, // Standard MUI palette — these replace the matching CSS vars @@ -200,36 +214,31 @@ export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { MuiTab: { styleOverrides: { root: ({ theme }) => ({ - fontSize: '14px', + fontSize: '0.875rem', fontWeight: 600, textTransform: 'none', color: theme.palette.text.disabled, - minHeight: '52px', - padding: '15px 16px', + minHeight: '3.25rem', + padding: '0.9375rem 1rem', '&.Mui-selected': { color: theme.palette.text.primary } }) } }, + // Menu and Select popups inherit the floating look from MuiPopover. MuiMenu: { styleOverrides: { - paper: ({ theme }) => ({ - border: `1px solid ${theme.palette.divider}`, - borderRadius: '14px !important', - boxShadow: 'var(--shadow-lg)', - marginTop: '6px', - backgroundColor: theme.palette.background.paper - }), - list: { padding: '6px' } + paper: { marginTop: '0.375rem' }, + list: { padding: '0.375rem' } } }, MuiMenuItem: { styleOverrides: { - root: { - borderRadius: '9px', - fontSize: '14px', + root: ({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + fontSize: '0.875rem', fontWeight: 500, - minHeight: '36px' - } + minHeight: '2.25rem' + }) } }, MuiTypography: { @@ -248,12 +257,12 @@ export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { }, MuiPopover: { styleOverrides: { - paper: ({ theme }) => ({ - border: `1px solid ${theme.palette.divider}`, - borderRadius: '14px !important', - boxShadow: 'var(--shadow-lg)', - backgroundColor: theme.palette.background.paper - }) + paper: ({ theme }) => floatingPaper(theme) + } + }, + MuiDialog: { + styleOverrides: { + paper: ({ theme }) => floatingPaper(theme) } }, MuiDivider: { @@ -262,26 +271,13 @@ export default function createDefaultTheme(themeType: 'light' | 'dark'): Theme { } }, MuiSelect: { - defaultProps: { - MenuProps: { - PaperProps: { - sx: { - border: '1px solid', - borderColor: 'divider', - borderRadius: '14px !important', - boxShadow: 'var(--shadow-lg)', - mt: '4px' - } - } - } - }, styleOverrides: { select: { '&.MuiInputBase-inputSizeSmall': { - paddingTop: '4px', - paddingBottom: '4px', - paddingLeft: '10px', - fontSize: '13px' + paddingTop: '0.25rem', + paddingBottom: '0.25rem', + paddingLeft: '0.625rem', + fontSize: '0.8125rem' } } } diff --git a/webui/src/layout/app-navbar.tsx b/webui/src/layout/app-navbar.tsx index dbcf4f5c8..a1041d13b 100644 --- a/webui/src/layout/app-navbar.tsx +++ b/webui/src/layout/app-navbar.tsx @@ -6,7 +6,7 @@ import { FunctionComponent, useContext, useEffect, useState } from 'react'; import { AppBar, Box, Toolbar } from '@mui/material'; -import { styled, useTheme } from '@mui/material/styles'; +import { alpha, styled, useTheme } from '@mui/material/styles'; import { Link as RouteLink, useLocation } from 'react-router-dom'; import { HeaderMenu } from '../header-menu'; import { MainContext } from '../context'; @@ -38,7 +38,7 @@ export const AppNavbar: FunctionComponent = () => { const { toolbarContent: ToolbarContent } = pageSettings.elements; const isHeroPage = useLocation().pathname === ExtensionListRoutes.MAIN; const theme = useTheme(); - const navbg = theme.palette.mode === 'dark' ? 'rgba(14, 14, 20, 0.74)' : 'rgba(255, 255, 255, 0.78)'; + const navbg = alpha(theme.palette.background.default, theme.palette.mode === 'dark' ? 0.74 : 0.78); const [scrolled, setScrolled] = useState(false); useEffect(() => { @@ -51,7 +51,7 @@ export const AppNavbar: FunctionComponent = () => { const showFan = scrolled; return ( - + {/* Solid frosted glass — visible when not scrolled */}