diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1b1d70..7587e53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,8 @@ jobs: cache-dependency-path: typescript/package-lock.json - run: npm ci working-directory: typescript + - run: npx playwright install --with-deps chromium + working-directory: typescript/web-ui-showcase - run: npm run check --workspaces working-directory: typescript diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0c201e5..68cc8f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,6 +28,8 @@ this repository. ## Sample requirements +### Focused samples + Every sample must: - Demonstrate one primary SDK concept. @@ -63,6 +65,10 @@ Keep the TypeScript and Python versions behaviorally equivalent so readers can switch between languages. When one concept has multiple independently copyable variants, keep them in one sample directory and list every entry in its README. +### Comprehensive application templates + +An application template may be language-specific when its framework and application architecture are part of what it teaches. It must demonstrate one coherent end-to-end application, keep SDK calls easy to locate, document module ownership, provide deterministic credential-free tests, and include an opt-in real-SDK smoke path. + ## Running the checks Run the same checks CI runs, and make sure they pass before opening a pull diff --git a/README.md b/README.md index 5ce3dec..22d27ac 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ Runnable, focused examples for building applications with the Qoder Agent SDK. Every sample is available in TypeScript and Python. +## Application templates + +| Template | What it demonstrates | Language | +| --- | --- | --- | +| [Web UI showcase](typescript/web-ui-showcase) | A complete local project agent with Sessions, streaming, inline interactions, MCP, Hooks, Tasks, Credits, Checkpoints, errors, shutdown, and resume | TypeScript | + ## Samples | Sample | What it demonstrates | TypeScript | Python | diff --git a/README.zh-CN.md b/README.zh-CN.md index 68f575a..badb7c5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -3,6 +3,12 @@ 本仓库提供面向实际开发者的 Qoder Agent SDK 示例。每个场景同时提供 TypeScript 和 Python 实现。 +## 应用样板 + +| 样板 | 展示内容 | 语言 | +| --- | --- | --- | +| [Web UI showcase](typescript/web-ui-showcase) | 中文浅色界面的本地项目 Agent;连续流式消息聚合显示,审批位于主界面,并覆盖 Session、MCP、Hooks、Task、Credits、Checkpoint、错误、退出和恢复 | TypeScript | + ## 示例列表 | 示例 | 展示内容 | TypeScript | Python | diff --git a/typescript/README.md b/typescript/README.md index 0788e3f..8d3bc79 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -9,3 +9,7 @@ npm install Run a sample with the script documented in its README; most use `npm start`. Complete the repository-level setup before running a sample. + +## Application template + +[`web-ui-showcase`](web-ui-showcase) is a complete Fastify and React application template backed by the Qoder TypeScript SDK. Its Chinese light-theme product UI covers first-send Workspace selection, automatically available Sessions, SDK-backed Command and Skill discovery, project-scoped `@ Files`, semantic streaming, locally expandable Tool details, inline Approval and MCP elicitation, Task state, Credits, safe errors, snapshot recovery, and graceful shutdown. Model and Permission Mode are selected directly in the Composer; MCP, Hooks, and Raw Events live in the default-closed SDK Console, and `/mcp` opens its MCP tab. Eligible user messages expose a Checkpoint dry-run, impact review, and confirmed files/conversation rewind. Task state and server/API control adapters are included, but the current React product has no reachable Task controls. The Chinese sample README routes readers to copyable SDK examples, a source tour, deterministic acceptance tests, a product trial guide, and the optional real-account smoke. diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 02b2c16..ad7492e 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -16,7 +16,8 @@ "hooks", "custom-tools", "subagents", - "external-session-storage" + "external-session-storage", + "web-ui-showcase" ] }, "ask-user-question": { @@ -660,6 +661,10 @@ "resolved": "tool-permissions", "link": true }, + "node_modules/@qoder-samples/typescript-web-ui-showcase": { + "resolved": "web-ui-showcase", + "link": true + }, "node_modules/@types/node": { "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", @@ -2146,6 +2151,4736 @@ "tsx": "^4.20.0", "typescript": "^5.9.0" } + }, + "web-ui-showcase": { + "name": "@qoder-samples/typescript-web-ui-showcase", + "dependencies": { + "@fastify/static": "^10.1.3", + "@fastify/websocket": "^11.0.0", + "@qoder-ai/qoder-agent-sdk": "^1.0.21", + "fastify": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "@playwright/test": "^1.55.0", + "@testing-library/jest-dom": "^6.9.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@types/ws": "^8.18.0", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.2.0", + "jsdom": "^29.1.1", + "tsx": "^4.22.0", + "typescript": "^5.9.0", + "vite": "^6.4.3", + "vitest": "^4.1.8", + "ws": "^8.21.0" + } + }, + "web-ui-showcase/node_modules/@fastify/static": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-10.1.3.tgz", + "integrity": "sha512-W6jqajYS974XjPjB5hQWoxPM8NKM4+p8YmQT6G5IbCa4uhdWSVadZUv75siy1wEA/3ty8RYdpBydfWeu9AqAqQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^2.0.0", + "@fastify/error": "^4.0.0", + "@fastify/send": "^4.0.0", + "content-disposition": "^2.0.1", + "fastify-plugin": "^6.0.0", + "fastq": "^1.17.1", + "glob": "^13.0.0" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/accept-negotiator": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.1.0.tgz", + "integrity": "sha512-F3EVbzWt+xcnVaOHmWyIlpuFtbxOln7HDZQsh09MtMmMm/CipMayNt8hnIL8VQi54u2ZociDbf+iluGYkf7B1A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/send": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-4.1.1.tgz", + "integrity": "sha512-BYo+EiaKwlxH+WetGk6hAs1d39iP0y1gqB8lGF/qwkJ9ZZ/cBY1vx5NvExb9Sc3yRMFjD5X4Eyh4e4+TzRkzdw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.2", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "^2.0.0", + "mime": "^3" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/send/node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/send/node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/content-disposition": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-2.0.1.tgz", + "integrity": "sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/fastify-plugin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-6.0.0.tgz", + "integrity": "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/fastq/node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "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" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/minimatch/node_modules/brace-expansion/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "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" + } + }, + "web-ui-showcase/node_modules/@fastify/static/node_modules/glob/node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/@fastify/websocket/-/websocket-11.3.0.tgz", + "integrity": "sha512-g89ag4BCcD9YP5wBZXixzoLnuf5j89p/sXFcfpCiv2pdEkYYukBEoK3heVzqsp0EAtszVDc2BBZG0KZqeAShIA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.3", + "fastify-plugin": "^6.0.0", + "ws": "^8.16.0" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/readable-stream/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/readable-stream/node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/duplexify/node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/@fastify/websocket/node_modules/fastify-plugin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-6.0.0.tgz", + "integrity": "sha512-fZOty7z3O7vOliF6d8bHE3wiEh1KcNnKEQensSgTk9C1DvN6nRLS++XVd86v33Hw/8u9Un8A1zDrQ8ujcQDHEg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/@playwright/test/node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "web-ui-showcase/node_modules/@playwright/test/node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "web-ui-showcase/node_modules/@playwright/test/node_modules/playwright/node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/@qoder-ai/qoder-agent-sdk": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/@qoder-ai/qoder-agent-sdk/-/qoder-agent-sdk-1.0.21.tgz", + "integrity": "sha512-G+pLA5HYZRjbytpDF7nlumbEgLvntA+yL2IGGy7BO6OtRI0c9RfzjwGnVNqr3mHQeiF7mi9b4jmrBNtI8I1wAA==", + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.27.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/aria-query/node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "web-ui-showcase/node_modules/@testing-library/dom/node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/redent/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/redent/node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/@testing-library/jest-dom/node_modules/redent/node_modules/strip-indent/node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "web-ui-showcase/node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/@testing-library/react/node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@testing-library/user-event": { + "version": "14.6.6", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.6.tgz", + "integrity": "sha512-Jbs9FpkkIDw8FgSc6kOVsOv8JuuqGAL7J4X1oot77JxAoDlkNn2GRkd0aYRVuQ+pVQAiHWVkE4rX/dkF5fBiCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "web-ui-showcase/node_modules/@types/react": { + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "web-ui-showcase/node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "web-ui-showcase/node_modules/@types/react/node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@types/react/node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/code-frame/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/code-frame/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/generator/node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist": { + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", + "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.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/baseline-browser-mapping": { + "version": "2.11.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.18.tgz", + "integrity": "sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/caniuse-lite": { + "version": "1.0.30001809", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", + "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==", + "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" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/electron-to-chromium": { + "version": "1.5.412", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz", + "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/node-releases": { + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/update-browserslist-db": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/update-browserslist-db/node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/browserslist/node_modules/update-browserslist-db/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-compilation-targets/node_modules/lru-cache/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helper-module-transforms/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "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" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/traverse/node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/types/node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping/node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/@jridgewell/remapping/node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/plugin-transform-react-jsx-self/node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@babel/plugin-transform-react-jsx-source/node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@babel/types/node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@babel/types/node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/@types/babel__core/node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "web-ui-showcase/node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk/node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk/node_modules/ansi-styles/node_modules/color-convert/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/chalk/node_modules/supports-color/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/rxjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "web-ui-showcase/node_modules/concurrently/node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/supports-color/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "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" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/wrap-ansi/node_modules/ansi-styles/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/cliui/node_modules/wrap-ansi/node_modules/ansi-styles/node_modules/color-convert/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/string-width/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "web-ui-showcase/node_modules/concurrently/node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "web-ui-showcase/node_modules/fastify": { + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.12.1.tgz", + "integrity": "sha512-FWi+tQvwxR/PeRX7Z2mhfEF5ozJ3jn9asiiclzKXNSzJRHAYcU924aIOKAdHFJ+YIKieh3cqr1IwCOvTr41B3Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.1.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/ajv-compiler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.6.tgz", + "integrity": "sha512-NtuzM0SfaMJbGlnjr9LWQUN5LzgSrbB8tf/wRZNas+4E1O/Nmzl53e7ruT61HDZyRCJGC6FxIogmNZO1c5ETBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/ajv-compiler/node_modules/fast-uri": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.3.tgz", + "integrity": "sha512-7+72G6vLt7jjNas8SmSATx2qeyRIjxeqO3i4IkmDTxlqYZRKANhOe1bnovcp4WZmvsYrp60WyqPyHqgRiX0yXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz", + "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/proxy-addr/node_modules/@fastify/forwarded": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.2.tgz", + "integrity": "sha512-NE8HgKLgYejV9lDpqkEFaDKMLYelJBVfHekhB0UKvX0ghagXRJqg68feg8er1NPXxG4N9i6vPxzt8E+3wHfcmA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/@fastify/proxy-addr/node_modules/ipaddr.js": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.5.0.tgz", + "integrity": "sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/avvio": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz", + "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/avvio/node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/avvio/node_modules/fastq/node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify/node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify/node_modules/@fastify/merge-json-schemas/node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.3.tgz", + "integrity": "sha512-7+72G6vLt7jjNas8SmSATx2qeyRIjxeqO3i4IkmDTxlqYZRKANhOe1bnovcp4WZmvsYrp60WyqPyHqgRiX0yXw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify/node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/fast-json-stringify/node_modules/json-schema-ref-resolver/node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/find-my-way": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.9.0.tgz", + "integrity": "sha512-sJsgZ1sQH2UDuowPuMKg8az7Qc8F0jnj+SKkFWU/+T0xcFlgV5skgXOGUqmQzOdmW6ALA7AhJINWx3qFBkbLHA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/find-my-way/node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/find-my-way/node_modules/fast-querystring/node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/find-my-way/node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/find-my-way/node_modules/safe-regex2/node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/light-my-request/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/light-my-request/node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/pino/node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/process-warning": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/fastify/node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "web-ui-showcase/node_modules/fastify/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "web-ui-showcase/node_modules/fastify/node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-color-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.2.0.tgz", + "integrity": "sha512-5+5LEmFuY1AjXdYhmgjTJogtQnP1evJ1zrBZGUNZ0thkpwnnmKxcHdAMn/OtFjAb25zA+jKDVYVRl+5G7rjv1A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.1", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-color-parser/node_modules/@csstools/color-helpers": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz", + "integrity": "sha512-gLNsunvwf3mCi5u5o46/Z/JcJMnhbHSaZ69rkgPzNM3J4s8hWwpPUQB6/tt0EDFyCiWzxANlx+2LJwpYj4zS1w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/css-color/node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/dom-selector/node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/dom-selector/node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@asamuzakjp/dom-selector/node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.8.tgz", + "integrity": "sha512-CpMLjAvwQg3BL5S0IeqsZNMH7EQrEWi0kLKOC13ZBF0ZwERiLWlibNPJr8G1kdU3Ms/r2KiNrF81pUh2HwAHdg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/css-tree/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/css-tree/node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/saxes/node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/tough-cookie/node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/tough-cookie/node_modules/tldts/node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/jsdom/node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/whatwg-url/node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/whatwg-url/node_modules/tr46/node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "web-ui-showcase/node_modules/jsdom/node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "web-ui-showcase/node_modules/react-dom/node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "web-ui-showcase/node_modules/react-dom/node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "web-ui-showcase/node_modules/react/node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "web-ui-showcase/node_modules/react/node_modules/loose-envify/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "web-ui-showcase/node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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 + } + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/vite/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "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.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/postcss/node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "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" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/postcss/node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/vite/node_modules/postcss/node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.5.tgz", + "integrity": "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.5", + "@rollup/rollup-android-arm64": "4.62.5", + "@rollup/rollup-darwin-arm64": "4.62.5", + "@rollup/rollup-darwin-x64": "4.62.5", + "@rollup/rollup-freebsd-arm64": "4.62.5", + "@rollup/rollup-freebsd-x64": "4.62.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", + "@rollup/rollup-linux-arm-musleabihf": "4.62.5", + "@rollup/rollup-linux-arm64-gnu": "4.62.5", + "@rollup/rollup-linux-arm64-musl": "4.62.5", + "@rollup/rollup-linux-loong64-gnu": "4.62.5", + "@rollup/rollup-linux-loong64-musl": "4.62.5", + "@rollup/rollup-linux-ppc64-gnu": "4.62.5", + "@rollup/rollup-linux-ppc64-musl": "4.62.5", + "@rollup/rollup-linux-riscv64-gnu": "4.62.5", + "@rollup/rollup-linux-riscv64-musl": "4.62.5", + "@rollup/rollup-linux-s390x-gnu": "4.62.5", + "@rollup/rollup-linux-x64-gnu": "4.62.5", + "@rollup/rollup-linux-x64-musl": "4.62.5", + "@rollup/rollup-openbsd-x64": "4.62.5", + "@rollup/rollup-openharmony-arm64": "4.62.5", + "@rollup/rollup-win32-arm64-msvc": "4.62.5", + "@rollup/rollup-win32-ia32-msvc": "4.62.5", + "@rollup/rollup-win32-x64-gnu": "4.62.5", + "@rollup/rollup-win32-x64-msvc": "4.62.5", + "fsevents": "~2.3.2" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.5.tgz", + "integrity": "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.5.tgz", + "integrity": "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.5.tgz", + "integrity": "sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.5.tgz", + "integrity": "sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.5.tgz", + "integrity": "sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.5.tgz", + "integrity": "sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.5.tgz", + "integrity": "sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.5.tgz", + "integrity": "sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.5.tgz", + "integrity": "sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.5.tgz", + "integrity": "sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.5.tgz", + "integrity": "sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.5.tgz", + "integrity": "sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.5.tgz", + "integrity": "sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.5.tgz", + "integrity": "sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.5.tgz", + "integrity": "sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.5.tgz", + "integrity": "sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.5.tgz", + "integrity": "sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.5.tgz", + "integrity": "sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.5.tgz", + "integrity": "sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.5.tgz", + "integrity": "sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.5.tgz", + "integrity": "sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.5.tgz", + "integrity": "sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.5.tgz", + "integrity": "sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.5.tgz", + "integrity": "sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.5.tgz", + "integrity": "sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "web-ui-showcase/node_modules/vite/node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vite/node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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" + } + }, + "web-ui-showcase/node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect/node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect/node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect/node_modules/@types/chai/node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect/node_modules/@types/chai/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/expect/node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/mocker/node_modules/estree-walker/node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/@vitest/utils/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/vitest/node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "web-ui-showcase/node_modules/vitest/node_modules/why-is-node-running/node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "web-ui-showcase/node_modules/vitest/node_modules/why-is-node-running/node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "web-ui-showcase/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/typescript/package.json b/typescript/package.json index 6f53655..9d5cc8c 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -12,6 +12,7 @@ "hooks", "custom-tools", "subagents", - "external-session-storage" + "external-session-storage", + "web-ui-showcase" ] } diff --git a/typescript/web-ui-showcase/.env.example b/typescript/web-ui-showcase/.env.example new file mode 100644 index 0000000..fb03241 --- /dev/null +++ b/typescript/web-ui-showcase/.env.example @@ -0,0 +1,12 @@ +QODER_WEBUI_HOST=127.0.0.1 +QODER_WEBUI_PORT=8787 +QODER_WEBUI_AUTH=cli +QODER_WEBUI_MODEL=auto +QODER_WEBUI_PERMISSION_MODE=default +QODER_WEBUI_EVENT_CAPACITY=1000 +QODER_WEBUI_CHECKPOINTS=true +QODER_WEBUI_RAW_EVENTS=true +# QODER_PERSONAL_ACCESS_TOKEN= +# QODER_WEBUI_DEV_ORIGIN=http://127.0.0.1:5173 +# QODER_WEBUI_DATA_DIR=/absolute/path/to/local/app-data +# QODER_WEBUI_MCP_CONFIG_FILE=/absolute/path/to/mcp-servers.json diff --git a/typescript/web-ui-showcase/.gitignore b/typescript/web-ui-showcase/.gitignore new file mode 100644 index 0000000..992da40 --- /dev/null +++ b/typescript/web-ui-showcase/.gitignore @@ -0,0 +1,5 @@ +.env +dist/ +coverage/ +playwright-report/ +test-results/ diff --git a/typescript/web-ui-showcase/README.md b/typescript/web-ui-showcase/README.md new file mode 100644 index 0000000..b6a7302 --- /dev/null +++ b/typescript/web-ui-showcase/README.md @@ -0,0 +1,184 @@ +# Qoder Agent SDK Web UI Showcase + +This is a complete local Web application example built with the Qoder TypeScript Agent SDK. It is neither a minimal API wrapper nor an SDK-provided UI. It demonstrates how to combine a long-lived `Query`, sessions, streaming messages, approvals, MCP, hooks, tasks, checkpoints, errors, recovery, and shutdown into a maintainable Fastify + React product. + +The browser uses a Chinese-language light UI. SDK concepts such as Session, Workspace, Model, Permission, MCP, Hook, Task, Checkpoint, Skill, Command, and Tool remain in English so they are easy to match with SDK types and source code. + +## Runtime Selection + +| Command | What it uses | Can it make real external calls? | Best for | +| --- | --- | --- | --- | +| `npm run dev` | The real SDK adapter and current authentication | Yes; creating a session or sending a message calls the real SDK | Local development and manual trials | +| `npm run build && npm start` | The real production build and current authentication | Yes; browser session operations call the real SDK | Trying the production form | +| `npm run check` | Real type/build checks and production-server smoke tests, plus unit, integration, and browser acceptance tests with injected fakes/fixtures | No real SDK session is created, and no real model call should be made | Default regression gate | +| `npm run test:smoke:real` | The installed real SDK, CLI/token authentication, and an isolated temporary session | **Yes** | Explicitly authorized real-path verification | + +Starting `npm run dev` or `npm start` does not itself call a model. The SDK path begins only after a session is created or resumed, or a message is sent. `npm run test:smoke:real` is explicitly opt-in. A `SKIP` result only means the authentication requirements were not met; it does not mean that real verification passed. + +## Learning Path + +- **5 minutes: run one Query.** Read [SDK Quick Start](docs/SDK_QUICK_START.md#single-query), copy the single-Query example, and understand `query()`, authentication, `cwd`, the asynchronous message stream, and `close()`. +- **15 minutes: understand the core Web UI wiring.** Continue with [Long-Lived Interactive Query](docs/SDK_QUICK_START.md#long-lived-interactive-query) and [How a Message Moves Through the System](docs/SDK_CODE_TOUR.md#how-a-message-moves-through-the-system), then compare them with `query-factory.ts`, `input-queue.ts`, and `session-controller.ts`. +- **30 minutes: verify the complete product.** Follow the [30-minute route in the Product Trial Guide](docs/PRODUCT_TRIAL_GUIDE.md#trial-routes) to exercise sessions, streaming messages, approvals, MCP, subagents, runtime controls, and checkpoints while recording the evidence boundary. + +## Documentation Map + +| Document | Audience | Question answered | Completion signal | +| --- | --- | --- | --- | +| This README | Developers opening the project for the first time | How do I select a runtime, install dependencies, start the app, and find the right documentation? | You can safely start the correct runtime | +| [SDK Quick Start](docs/SDK_QUICK_START.md) | Developers using the Qoder TypeScript Agent SDK for the first time | What are copyable examples for one-shot and long-lived queries? | The examples type-check and you understand the Query lifecycle | +| [SDK Code Tour](docs/SDK_CODE_TOUR.md) | Developers preparing to reuse this example's architecture | How do SDK calls map to the server, events, and browser? | You can trace a product capability to SDK symbols, source files, and tests | +| [Product Trial Guide](docs/PRODUCT_TRIAL_GUIDE.md) | Product, design, test, and integration developers | How do I operate the UI and decide whether a capability was actually verified? | Every case has an evidence level, determinism level, and side-effect record | +| [SDK Adapter Maintenance Guide](src/server/sdk/README.md) | Maintainers changing `src/server/sdk/` | What is each adapter file responsible for, and what is the dependency direction? | You can change an adapter without widening the SDK boundary | + +## Quick Start + +### Requirements + +- Node.js 22 or later. +- Chromium when running Playwright acceptance tests. +- An existing `qodercli` login or Qoder personal access token when manually trying the real SDK. + +Install dependencies from the `typescript/` workspace: + +```bash +cd typescript +npm install +npx playwright install chromium +cd web-ui-showcase +cp .env.example .env +``` + +### Authentication + +The default is `QODER_WEBUI_AUTH=cli`, which makes the server call `qodercliAuth()` to reuse the local login. To use an access token, set the following in the environment or an uncommitted `.env` file: + +```dotenv +QODER_WEBUI_AUTH=access-token +QODER_PERSONAL_ACCESS_TOKEN=your-token +``` + +Credentials are read only by the server. Do not put a token in React environment variables, browser requests, localStorage, screenshots, or repository commits. + +### Development Mode + +```bash +npm run dev +``` + +Fastify listens on `127.0.0.1:8787` by default, and Vite listens on `127.0.0.1:5173`. Open [http://127.0.0.1:5173](http://127.0.0.1:5173). + +### Production Build + +```bash +npm run build +``` + +```bash +QODER_WEBUI_HOST=127.0.0.1 QODER_WEBUI_PORT=8787 npm start +``` + +Open [http://127.0.0.1:8787](http://127.0.0.1:8787). + +## Product Behavior Summary + +- **First send and Workspace:** You can type before selecting a Workspace. The first Send opens the native directory picker, preserves the draft, registers the canonical directory, creates a session, and submits the same message. +- **Session:** Each session is permanently bound to one Workspace. Selecting a session loads projected history and makes the corresponding SDK Query available; concurrent availability requests are coalesced. +- **Semantic transcript:** Assistant deltas and the final message merge into one semantic item. Tool input, status, and result merge by tool-use id. Subagent internals stay in separate details. SDK control acknowledgements do not become ordinary chat cards. +- **Interactions:** Approvals, `AskUserQuestion`, and MCP elicitation are completed inline. Command failures are owned by the control that initiated them; only connection or realtime protocol errors use global notices. +- **Composer:** `/model`, `/permissions`, and `/context` control the current session. `/mcp` opens the MCP tab in SDK Console. `@ Files` searches only paths in registered directories and does not read file contents. +- **Asynchronous input:** The application preserves the SDK's `priority` and `shouldQuery`; the product Composer defaults to `next` and `true`. `delivered` means only that the message reached the SDK input transport, not that the turn completed. +- **MCP and Hooks:** MCP status, Hooks, Raw Events, Skills, Agents, Plugins, and Account are in SDK Console, which is closed by default. MCP elicitation still appears inline. +- **Checkpoint:** Eligible user messages expose Checkpoint actions. The user selects files, conversation, or both; runs a dry run; reviews the impact summary; and then confirms execution. +- **Task:** SDK Task messages are projected, and the server and API client adapt `backgroundTasks()` / `stopTask()`. The current product has no reachable Task list or control button, so starting or stopping a Task through natural language is not evidence that the UI directly called these two methods. + +See the [Product Trial Guide](docs/PRODUCT_TRIAL_GUIDE.md) for complete operating procedures. + +## Architecture and Boundaries + +```mermaid +flowchart LR + Browser["React browser\nproduct view model"] -->|validated REST commands| API["Fastify API"] + API --> Services["Workspace / Session services"] + Services --> Adapters["SDK adapter layer"] + Adapters --> SDK["Qoder TypeScript SDK"] + Adapters --> Journal["redacted, ordered event journal"] + Journal -->|snapshot + events| Browser + Services --> Files["registered local directories"] +``` + +The browser does not depend on the SDK: + +- [`src/shared/`](src/shared/) defines Zod-validated commands, snapshots, events, and browser-safe view models. +- [`src/client/transport/`](src/client/transport/) handles commands and ordered realtime recovery; [`src/client/store/`](src/client/store/) reduces snapshots and events into product state. +- Only [`src/server/sdk/`](src/server/sdk/) may import `@qoder-ai/qoder-agent-sdk`. +- [`src/server/services/`](src/server/services/) depends only on application ports and owns product policy; [`src/server/api/`](src/server/api/) validates and delegates. + +`npm run check:boundary` runs [`scripts/check-sdk-import-boundary.mjs`](scripts/check-sdk-import-boundary.mjs) to enforce this dependency direction. + +### Four Layers to Distinguish When Reusing the Example + +1. **SDK-required contracts:** `query()`, asynchronous iteration over `Query`, `SDKUserMessage`, session state, interaction callbacks, the public session catalog, and rewind APIs. +2. **Showcase policies:** the narrow `QueryPort`, Composer defaults, capability gates, command ownership, and the requirement that a Checkpoint be idle, current, and single-use. +3. **Product infrastructure:** Workspace canonicalization, parent sessions published before child events, snapshot/replay consistency, mutation fences, browser redaction, and constrained file discovery. +4. **Optional diagnostics:** Hooks/Raw Events in SDK Console and real-account smoke testing. They help teaching and troubleshooting but are not SDK-mandated UI. + +See the [SDK Code Tour](docs/SDK_CODE_TOUR.md) for a more detailed end-to-end mapping. + +## Workspace and Local Security + +[`workspace-service.ts`](src/server/services/workspace-service.ts) registers only canonical directories produced by the native picker or an explicit local path. When creating a session, the browser sends a Workspace id instead of an arbitrary `cwd`; an existing session cannot silently switch its root directory. + +[`workspace-file-service.ts`](src/server/services/workspace-file-service.ts) searches the Workspace first and then explicitly allowed directories. All roots share one scan budget. The server rechecks real paths, skips symlinks and generated directories, and returns paths only. The browser debounces requests by 200 ms and cancels scans for stale requests or disconnected HTTP clients. Final Tool permissions are still determined by the SDK runtime and Permission Mode. + +The service accepts only `127.0.0.1`, `::1`, or `localhost`. REST and WebSocket use the same exact Origin allowlist, with CSP, Zod validation, and browser-projection redaction enabled. This is a single-user local example, not a hardened hosted service. Do not expose the port directly through a reverse proxy; add application authentication, authorization, and tenant isolation before hosting it. + +## MCP Configuration + +Each session includes a read-only in-process `showcase_project` MCP server. Additional server-side MCP configuration can be loaded through `QODER_WEBUI_MCP_CONFIG_FILE`: + +```json +{ + "docs": { + "type": "http", + "url": "http://127.0.0.1:9000/mcp", + "tools": [{ "name": "search", "permission_policy": "always_ask" }] + } +} +``` + +Load only trusted configuration. Remote headers, subprocess environments, and OAuth tokens stay on the server. When user action is required, the server publishes only a protocol-validated authorization URL and redacted, bounded status to the browser. A pasted callback URL is submitted to the server in one validated request; after success it is cleared from component-local input and is not written to snapshots, realtime events, or diagnostics. + +## Verification + +The default gate does not require a Qoder account and should not make real model calls: + +```bash +npm run check +``` + +It runs TypeScript checks, the SDK import boundary check, unit tests, integration tests, a production build, a production HTTP/WebSocket smoke test, and Playwright in sequence. The [`test/e2e/fixture-server.ts`](test/e2e/fixture-server.ts) used by Playwright replaces the external Query, Workspace repository, native directory picker, and session catalog while retaining the real browser, Fastify routes, application services, session controller, semantic projection, journal, and realtime recovery. The production smoke test starts the actual build artifact but does not create a session or call a model. + +During iteration, run: + +```bash +npm run typecheck +npm run check:boundary +npm run test:unit +npm run test:integration +npm run build +npm run test:smoke:production +npm run test:e2e +``` + +Run the real-account check only when real verification is explicitly intended: + +```bash +npm run test:smoke:real +``` + +The real smoke test creates an isolated temporary Workspace and session, completes one model turn, verifies SDK history, resumes the session, and deletes only that temporary session and directory. Initialization, the first successful result, resume, shutdown, and cleanup all have deadlines. On failure it still attempts to close the Query, delete the session record, and remove the temporary directory independently. + +## Extending the Showcase + +When adding a browser-visible capability, first define the shared command/event/view model, then implement the server-side SDK adapter and application policy. Next, reduce the event into client state and place the interaction at a natural product entry point. SDK objects and credentials must stay on the server. Accepted commands use `commandId` to correlate asynchronous failures. Finally, add deterministic tests that prove assembled event ordering and register the capability in the [SDK Code Tour capability map](docs/SDK_CODE_TOUR.md#capability-map). diff --git a/typescript/web-ui-showcase/docs/PRODUCT_TRIAL_GUIDE.md b/typescript/web-ui-showcase/docs/PRODUCT_TRIAL_GUIDE.md new file mode 100644 index 0000000..8a7bbeb --- /dev/null +++ b/typescript/web-ui-showcase/docs/PRODUCT_TRIAL_GUIDE.md @@ -0,0 +1,666 @@ +# Web UI Showcase Product Trial and SDK Capability Acceptance Guide + +This guide is for product, design, test, and SDK integration teams. It is not an API reference. It is a set of acceptance cases that can be performed in the Web UI, with the runtime, evidence level, determinism, side effects, procedure, expected result, and failure criteria stated for every case. + +The guide targets `@qoder-ai/qoder-agent-sdk` 1.0.21 and the current `web-ui-showcase`. Use [Runtime Selection in the package README](../README.md#runtime-selection) as the authority for installation, authentication, startup commands, and the distinction between the real SDK and fixtures. See the [SDK Code Tour capability map](SDK_CODE_TOUR.md#capability-map) for source-code mappings. + +## Confirm What You Are Verifying + +Manual procedures in this guide use `npm run dev` or the production build by default, so they connect to the **real SDK and current authentication**. A natural-language prompt may cause real model, Tool, file, or remote MCP calls. + +`npm run check` includes real type checks, a production build, a server smoke test that creates no session, and deterministic unit, integration, and Playwright acceptance tests with injected fakes/fixtures. The Playwright fixture does not execute every prompt in this guide verbatim. It proves that the real browser, Fastify routes, application services, Session controller, projection, journal, and recovery logic assemble correctly around controlled events. The default gate does not prove real account, model, CLI, or remote MCP behavior. + +### Evidence Levels + +| Level | What it proves | What it cannot prove by itself | +| --- | --- | --- | +| **Direct interface** | A UI operation calls the listed public SDK interface through the application adapter | That the model will choose a particular strategy or an external service will be available | +| **Message stream** | State or events can be observed in the asynchronous SDK message stream from the `Query` | That a dedicated UI control exists for the capability | +| **Model behavior** | Natural language prompts the model to choose a Tool, Subagent, or execution strategy | That the application directly called a particular Query control method | + +### Determinism + +- **High:** Primarily a direct UI/API operation; the result does not depend on model choice. +- **Medium:** A direct interface can be verified, but timing, runtime capability, or network conditions may vary. +- **Low:** Primarily depends on whether the model follows the prompt or selects the requested Tool. + +Low determinism does not indicate a defect. It means acceptance evidence must record the actual SDK event or Tool instead of relying only on an Assistant statement. + +## Trial Routes + +| Time | Cases | Goal | +| --- | --- | --- | +| 5 minutes | [CASE-01](#case-01-create-a-session-and-observe-streaming-messages), [CASE-11](#case-11-model-permission-mode-and-context) | Complete the Session/stream path and verify that Model, Permission, and Context controls do not pollute the conversation | +| 15 minutes | CASE-01, [CASE-02](#case-02-interrupt-the-current-generation), [CASE-03](#case-03-allow-a-tool-once), [CASE-06](#case-06-call-the-built-in-mcp-server), [CASE-07](#case-07-observe-hooks-and-raw-events) | Cover creation, stop, Approval, MCP, and Hook/Raw Event behavior | +| 30 minutes | The 15-minute route plus [CASE-08](#case-08-start-and-inspect-a-subagent), [CASE-14](#case-14-manage-resume-and-fork-sessions), [CASE-17](#case-17-preview-and-execute-a-checkpoint) | Add Subagent, session management/recovery, and Checkpoint coverage | + +The remaining cases are for focused acceptance. Actual duration may increase when the model or network is slow. + +## General Preparation and Cleanup + +1. Start the real application using the [root README](../README.md), then choose a local directory dedicated to the trial as the Workspace. +2. Do not select a directory containing credentials, private data, or important unbacked-up changes. +3. Begin with Permission Mode set to `default` so Approvals are observable. +4. Write cases use only `qoder-sdk-showcase-trial.txt` and `qoder-sdk-showcase-denied.txt`. +5. Long-running cases use only `sleep 300` or `sleep 20`. Confirm that each process has stopped before finishing. +6. Hooks/Raw Events in SDK Console are diagnostic evidence, not the ordinary product UI. +7. Record the session title, case, actual behavior, SDK message/Tool, screenshots, and cleanup result. + +## CASE-01 Create a Session and Observe Streaming Messages + +> **Runtime:** Real SDK | **Evidence:** Direct interface + message stream + model behavior | **Determinism:** Medium | **Side effects:** Creates a persistent session and makes a real model turn; the prompt is read-only + +**Prerequisites** + +- Stay on the home screen without preselecting a Workspace. The Composer must not contain another draft that needs to be kept. + +**Input and procedure** + +```text +First explain in one sentence how you plan to inspect the current project. Then summarize the project's directory structure in three points. This task is read-only; do not modify files. +``` + +1. Send the prompt directly and choose the dedicated Workspace when requested. +2. Wait for the Assistant to finish, refresh the page, and select the session again. + +**Expected result** + +- The first draft still appears as one user message after directory selection. +- The sidebar contains a new session bound to that Workspace. +- Assistant deltas update one semantic message, which reaches completed state without a duplicate final message. +- The session and user/Assistant history are restored after refresh. + +**SDK mapping and evidence** + +- Direct: `query({ prompt, options })`, `Options.auth`, `cwd`, and `sessionId`. +- Message stream: `includePartialMessages` and `AsyncIterable`. +- Recovery: `resume` and `getSessionMessages()`. + +**Failure criteria and cleanup** + +- Loss of the first input, a duplicated Assistant message, or missing history after refresh is a failure. +- Keep the session for later cases if useful; delete its record from the session menu after all trials. + +## CASE-02 Interrupt the Current Generation + +> **Runtime:** Real SDK | **Evidence:** Direct interface + message stream | **Determinism:** Medium | **Side effects:** Two real model turns; no file changes + +**Prerequisites** + +- Use an available, idle session. + +**Input and procedure** + +```text +Write an architecture description of the current project that is at least 3,000 words. Start with the main text and do not call tools. +``` + +1. After the Assistant begins sustained output, click Stop (`停止`) in the Composer. +2. Send: + +```text +Reply with INTERRUPT_RECOVERED only. +``` + +**Expected result** + +- The original Assistant message stops growing and shows interrupted state. +- The session remains open, the Composer becomes usable again, and the next turn completes normally. +- Queued items are retained or cancelled according to `still_queued` and optional `cancelled` UUIDs in the interrupt receipt. + +**SDK mapping and evidence** + +- Direct: `Query.interrupt()`. +- Message stream: `idle`, `running`, and `requires_action` from `session_state_changed`. Inferring idle from a result exists only for older-runtime compatibility. + +**Failure criteria and cleanup** + +- Continued output after Stop, unconditional removal of every queued message, or an unusable session is a failure. +- No additional cleanup. + +## CASE-03 Allow a Tool Once + +> **Runtime:** Real SDK | **Evidence:** Model behavior + direct interface + message stream | **Determinism:** Low | **Side effects:** Attempts to create one local file and makes a real Tool call + +**Prerequisites** + +- Permission Mode is `default`; the Workspace is writable and the target file does not exist. + +**Input and procedure** + +```text +Use the file-writing tool to create qoder-sdk-showcase-trial.txt in the project root. Its content must be exactly APPROVAL_ALLOWED. Do not use Bash. If authorization is required, wait for me to confirm it in the UI. +``` + +1. Open details on the Approval card and verify the Tool name, path, and content. +2. Click Allow Once (`允许一次`), wait for the Tool to complete, and expand the Tool row to inspect Input/Result. + +**Expected result** + +- The write does not complete before approval. +- After approval, Tool state moves from waiting/running to completed, and the file content is exactly `APPROVAL_ALLOWED`. +- The same Approval can be answered only once. + +**SDK mapping and evidence** + +- The model first selects the writing Tool. The UI then directly returns `{ behavior: "allow" }` from the `Options.canUseTool` callback. +- Always Allow (`始终允许`) also uses SDK-provided `PermissionUpdate` suggestions; this case verifies one-time approval only. + +**Failure criteria and cleanup** + +- Writing before approval, a mismatch between the card and the path, or a card that remains pending after approval is a failure. +- Delete `qoder-sdk-showcase-trial.txt` after verification. It may be kept temporarily for the Checkpoint case and reverted there. + +## CASE-04 Deny Tool Approval + +> **Runtime:** Real SDK | **Evidence:** Model behavior + direct interface + message stream | **Determinism:** Low | **Side effects:** Attempts a write but should create no file + +**Prerequisites** + +- Permission Mode is `default`; confirm that `qoder-sdk-showcase-denied.txt` does not exist. + +**Input and procedure** + +```text +Use the file-writing tool to create qoder-sdk-showcase-denied.txt with the content SHOULD_NOT_EXIST. Do not use Bash. +``` + +1. On the first Approval, enter `Product trial: deny write` as the denial reason. +2. Leave Also stop the current turn (`同时停止当前轮次`) unchecked and deny the request. +3. Send the same prompt again. On the new Approval, select Also stop the current turn (`同时停止当前轮次`), deny it, and compare the two turns. +4. Send an ordinary read-only question to confirm that the session remains usable. + +**Expected result** + +- No file is created. The current turn shows permission-denied/unable-to-complete state rather than a global connection error. +- Turn behavior differs between `interrupt: false` and `true`; the session remains usable after denial. + +**SDK mapping and evidence** + +- `PermissionResult`: `behavior: "deny"`, `message`, and `interrupt`. +- The user's reason returns to the Tool flow; the browser receives bounded and redacted error text. + +**Failure criteria and cleanup** + +- Appearance of the target file is a severe failure. Record the Tool, Permission Mode, and Raw Event. +- If an environment fault creates the file, delete it immediately and stop write trials in that session. + +## CASE-05 Structured AskUserQuestion + +> **Runtime:** Real SDK | **Evidence:** Model behavior + direct callback | **Determinism:** Low | **Side effects:** One real model turn; no file changes + +**Prerequisites** + +- The session is idle with no other pending Approval or elicitation. + +**Input and procedure** + +```text +Before answering, you must use the AskUserQuestion tool to ask one single-choice question. Use the title "Inspection scope", the question "What should this inspection cover?", and the options "README only" and "Entire project". After receiving my answer, summarize only the scope I selected. Do not modify files. +``` + +1. Select an option or enter a custom answer. +2. Click Submit Answer (`提交回答`) and wait for the Agent to continue. + +**Expected result** + +- A structured Agent Question (`Agent 提问`) card appears instead of a plain-text question. +- The Agent remains waiting before submission, continues only once afterward, and gives a final response consistent with the selection. + +**SDK mapping and evidence** + +- Whether the model selects `AskUserQuestion` is model behavior. +- Once triggered, the Demo recognizes the input in `CanUseTool` and directly submits the answer through `PermissionResult.updatedInput`. + +**Failure criteria and cleanup** + +- No structured card, duplicate submission, answer mismatch, or a session permanently stuck in `requires_action` is a failure. +- No additional cleanup. + +## CASE-06 Call the Built-In MCP Server + +> **Runtime:** Real SDK | **Evidence:** Model behavior + message stream | **Determinism:** Low | **Side effects:** Real model plus read-only in-process MCP Tool; does not read file contents + +**Prerequisites** + +- The MCP tab in SDK Console shows `showcase_project` as connected. + +**Input and procedure** + +```text +Use only the list_project_entries tool provided by the showcase_project MCP Server to list the current project's top-level files and directories. Do not use Bash, Glob, Read, or any other file tool. Finally, state the name of the MCP tool you called. +``` + +1. Inspect the MCP Tool row and expanded result in the conversation. +2. Compare the top-level names and types with the Workspace. + +**Expected result** + +- The model calls the Tool corresponding to `mcp__showcase_project__list_project_entries` instead of another file Tool. +- The result contains only top-level names/types, not file contents. The read-only `always_allow` Tool normally requires no write Approval. + +**SDK mapping and evidence** + +- `createSdkMcpServer()`, `tool()`, `Options.mcpServers`, and `Query.mcpServerStatus()`. +- Connection status is read directly; whether the model uses the requested Tool remains model behavior. + +**Failure criteria and cleanup** + +- If the MCP tab is disconnected, record a configuration/runtime failure. If it is connected but the model does not use the Tool, record a model-guidance problem rather than immediately failing the MCP adapter. +- No cleanup for the read-only in-process Tool. + +## CASE-07 Observe Hooks and Raw Events + +> **Runtime:** Real SDK | **Evidence:** Message stream + callback observation | **Determinism:** Medium | **Side effects:** Reads projected diagnostics only; depends on a previous real call + +**Prerequisites** + +- Complete one Tool call in CASE-03 or CASE-06 and make sure SDK Console can be opened. + +**Input and procedure** + +1. In Hooks, find records such as `SessionStart`, `UserPromptSubmit`, `PreToolUse`, and `PostToolUse`. +2. Distinguish `source: "callback"` / `phase: "observation"` from `source: "sdk-event"`, and inspect hook-id correlation. +3. In Raw Events, expand Assistant, Tool, result, or system messages. +4. Inspect how credential fields such as token, Authorization, and cookie, and oversized MCP metadata, are projected. + +**Expected result** + +- Callback observations and SDK lifecycle records remain two valid sources and are not incorrectly merged. +- Raw Events help explain semantic ordering in the main UI. +- Credential-shaped values are redacted; oversized values are bounded by depth, node, byte, and count limits. + +**SDK mapping and evidence** + +- `Options.hooks`, `HookCallback`, `HookJSONOutput`, `includeHookEvents`, and the SDK message stream. +- Browser bounding/redaction is Showcase infrastructure, not UI automatically produced by the SDK. + +**Failure criteria and cleanup** + +- Plaintext credentials reaching the browser is a severe failure. If a Hook is missing, first confirm whether the runtime emitted the corresponding event. +- No additional cleanup. + +## CASE-08 Start and Inspect a Subagent + +> **Runtime:** Real SDK | **Evidence:** Model behavior + message stream + direct catalog | **Determinism:** Low | **Side effects:** Starts a real Subagent; the prompt is read-only + +**Prerequisites** + +- The runtime supports the `Agent` Tool and the session is idle. + +**Input and procedure** + +```text +You must use the Agent tool to start a read-only subagent. Ask it to inspect README and package.json in the current project and return a three-point summary. The main Agent must not call Read, Glob, or Bash itself. +``` + +1. Wait for an `Agent` Tool to appear in the main conversation. +2. Click the Tool to open Subagent details and expand its internal Tools. + +**Expected result** + +- Intermediate Assistant/Tool records from the Subagent do not enter the main conversation. +- Details show the task instruction, Assistant messages, and internal Tools in the correct order. Closing details does not affect the session. + +**SDK mapping and evidence** + +- Realtime `parent_tool_use_id` separates main-agent and subagent records. +- Details directly read public history through `listSubagents(sessionId, options)` and `getSubagentMessages(sessionId, agentId, options)`. + +**Failure criteria and cleanup** + +- Duplicated Subagent internals in the main conversation, details attached to the wrong Tool, or the browser scanning SDK transcript files is a failure. +- No local-file cleanup; the Subagent record remains with the session. + +## CASE-09 Background Shell Task and Foreground Conversation + +> **Runtime:** Real SDK | **Evidence:** Model behavior + Task message stream | **Determinism:** Low | **Side effects:** Starts a local `sleep` process for up to 300 seconds; cleanup is mandatory + +**Prerequisites** + +- The Workspace permits Bash. You can confirm and stop the process after the trial. + +**Input and procedure** + +Send first: + +```text +Use Bash to start a background Shell task that runs sleep 300. As soon as the task has started, reply with BACKGROUND_STARTED. Do not wait for it to finish and do not stop it immediately. +``` + +After the reply, send: + +```text +Do not wait for or stop the background task. Reply with FOREGROUND_CHAT_OK only. +``` + +Finally, send: + +```text +Stop the sleep 300 background task you started. Confirm that it is no longer running, then reply with BACKGROUND_STOPPED. +``` + +**Expected result** + +- The first turn does not wait 300 seconds, and the second turn completes while the background task exists. +- Raw Events may contain `task_started`, `task_updated`, `background_tasks_changed`, or `task_notification`; the combination depends on the runtime. +- No `sleep 300` process remains after the final turn. + +**SDK mapping and evidence** + +- This case primarily proves model behavior and the SDK Task message stream. +- “Run in the background” in natural language does not mean the UI called `Query.backgroundTasks(toolUseId)`. “Stop” does not mean it called `Query.stopTask(taskId)`. +- Server routes/services and the API client adapt both methods, but **the current product has no reachable Task control**. + +**Failure criteria and cleanup** + +- A blocked foreground, clearly mismatched Task state, or an unstopped process is a failure. +- Independently confirm and terminate `sleep 300`; do not rely only on the Assistant's “stopped” statement. + +## CASE-10 Queue and Cancel a Message While Running + +> **Runtime:** Real SDK | **Evidence:** Direct interface + message stream | **Determinism:** Low (timing-sensitive) | **Side effects:** Starts a local `sleep` for up to 20 seconds and makes real model turns + +**Prerequisites** + +- The session is idle and Bash is available. + +**Input and procedure** + +Send first: + +```text +Use Bash to run sleep 20 in the foreground. After the command finishes, reply with LONG_TURN_DONE. +``` + +Immediately send while it is running: + +```text +This is a queued-message test. Reply with QUEUED_MESSAGE_DONE only. +``` + +Before the second message is processed, click Cancel Queued Message (`取消排队消息`). + +**Expected result** + +- Input remains available while the session is running; the second message shows waiting/processing state. +- The application queue cancels a locally undelivered item. If already yielded to the SDK, the application calls `cancelAsyncMessage(uuid)`. +- If the SDK has already processed it, the UI clearly states that it can no longer be cancelled. Queue state does not leak into another session. + +**SDK mapping and evidence** + +- `AsyncIterable`, `priority`, `shouldQuery`, and `cancelAsyncMessage()`. +- `delivered` means transport delivery only. The SDK may combine inputs, and results do not identify a unique source UUID. + +**Failure criteria and cleanup** + +- The original message being overwritten, an unexplained execution after cancellation, or any result clearing every input is a failure. +- Wait for `sleep 20` to end. If interruption leaves it behind, stop the process manually. + +## CASE-11 Model, Permission Mode, and Context + +> **Runtime:** Real SDK | **Evidence:** Direct interface | **Determinism:** High (subject to runtime capability) | **Side effects:** Changes settings for the current session; does not send a model message by itself + +**Prerequisites** + +- The current session is live and the runtime can return at least one Model. + +**Input and procedure** + +1. Enter `/model` in the Composer, choose the suggestion, and switch to an available Model. +2. Enter `/permissions` and inspect `default`, `acceptEdits`, and `auto` in turn. +3. Enter `/context`, then enter `/context extra`. +4. Enter `/mcp`. +5. Inspect conversation history. + +**Expected result** + +- `/model` and `/permissions` focus their controls; the current value is visible after selection. +- `/context` refreshes the Context indicator; the variant with an argument shows a local error. +- `/mcp` opens the MCP tab in SDK Console. +- These product-control commands do not become user messages. Failures belong to their controls instead of a global banner. + +**SDK mapping and evidence** + +- `getAvailableModels({ fetchStrategy: "live" })`, `setModel()`, `setPermissionMode()`, `getContextUsage()`, and `mcpServerStatus()`. + +**Failure criteria and cleanup** + +- A setting leaking to another session, a control command entering the transcript, or a local failure becoming a global disconnect is a failure. +- Restore Permission Mode to the value required by later cases. + +## CASE-12 Commands, Skills, and Prompt Suggestions + +> **Runtime:** Real SDK | **Evidence:** Direct interface + message stream | **Determinism:** Medium | **Side effects:** Selecting a Skill may submit real input; clicking a Suggestion should not submit + +**Prerequisites** + +- Session initialization is complete. Finish one model turn first if you want to observe a dynamic Prompt Suggestion. + +**Input and procedure** + +1. Enter `/` in an empty Composer and browse suggestions with the keyboard. +2. Inspect the fixed product commands `/context`, `/model`, `/mcp`, and `/permissions`. +3. Compare with Skills in SDK Console and, when the runtime supports them, inspect `compact` / `compress` / `summarize`. +4. After a turn completes, click one Prompt Suggestion without sending it. + +**Expected result** + +- Only SDK Commands/Skills with an execution policy are advertised. +- Selecting, filling, and sending a Suggestion are three separate actions; clicking only fills the draft. +- The active keyboard option always remains visible. + +**SDK mapping and evidence** + +- `promptSuggestions: true`, `initializationResult()`, `supportedCommands()`, and the `prompt_suggestion` SDK message. + +**Failure criteria and cleanup** + +- Advertising a command that cannot execute, clicking a Suggestion and automatically invoking the model, or joining Suggestions into one message is a failure. +- Clear the draft; no additional cleanup. + +## CASE-13 Additional Directories and `@ Files` + +> **Runtime:** Real SDK + Showcase local infrastructure | **Evidence:** Direct interface | **Determinism:** High | **Side effects:** Expands allowed directories for the current Query; local scans return paths only + +**Prerequisites** + +- Prepare a dedicated additional directory without sensitive files. + +**Input and procedure** + +1. In Settings, click Add Directory (`添加目录`) and select the dedicated directory with the native picker. +2. Return to the Composer, enter `@` plus part of a filename, and select a suggestion without sending. +3. Change the search text quickly and observe that a stale result does not replace a newer result. + +**Expected result** + +- SDK-accepted directories appear in the allowed list. +- Suggestions can come from the Workspace or additional directory and insert paths only, without reading content or calling the model. +- Search stays within bounds; stale requests are debounced/aborted. + +**SDK mapping and evidence** + +- Direct SDK interface: `Query.addDirectories(directories)`. +- Native selection, canonicalization, the 200 ms debounce, cancellation, Workspace-first ordering, shared cross-root budget, and `@ Files` UI are all Showcase implementation, not SDK file search. + +**Failure criteria and cleanup** + +- Returning an out-of-root path, leaking content, escaping through a symlink, or allowing an older result to replace a new query is a severe failure. +- The allowed directory may remain until the session ends. Delete the dedicated session when it is no longer needed. + +## CASE-14 Manage, Resume, and Fork Sessions + +> **Runtime:** Real SDK | **Evidence:** Direct catalog + Query resume | **Determinism:** High | **Side effects:** Changes session metadata, creates a fork, and can delete session records at the end + +**Prerequisites** + +- Use a dedicated trial session with at least one completed turn. + +**Input and procedure** + +1. Open `…` on the session row and rename it manually. Then try Generate Title with SDK (`使用 SDK 生成标题`). +2. Add a tag, run `Fork`, and confirm that the original session remains. +3. Refresh and select the original session and fork separately. +4. Run Delete Record (`删除记录`) on a dedicated session. + +**Expected result** + +- Each operation affects only the selected session, and a generated title persists after completion. +- The fork has an independent id/history and does not modify the original history. +- Both restore after refresh. Deleting a session record does not delete Workspace files. + +**SDK mapping and evidence** + +- `listSessions()`, `getSessionInfo()`, `getSessionMessages()`, `renameSession()`, `tagSession()`, `forkSession()`, and `deleteSession()`. +- `generateSessionTitle(description, { persist: true })`; Query recovery uses `Options.resume`. + +**Failure criteria and cleanup** + +- A menu affecting an adjacent row, a fork overwriting the original, filesystem content being deleted, or recovery losing the first message is a failure. +- Delete unneeded original/fork trial records. Manage the Workspace directory separately. + +## CASE-15 Account, Agents, and Plugins + +> **Runtime:** Real SDK | **Evidence:** Direct interface | **Determinism:** High/medium (depends on account and runtime) | **Side effects:** Reads account/usage; Plugin reload refreshes runtime extensions + +**Prerequisites** + +- The session is live and the current account permits the relevant capability. + +**Input and procedure** + +1. Inspect Agents in SDK Console. +2. In Plugins, click Reload Plugins (`重新加载 Plugins`). +3. In Account, inspect the account, Credits/Usage, and SDK/CLI versions. + +**Expected result** + +- Switching tabs sends no model message. +- Plugins are reread after reload; unavailable capabilities show a local error. +- The browser receives only redacted account fields required for display. + +**SDK mapping and evidence** + +- `supportedAgents()`, `listPlugins()`, `reloadPlugins()`, `accountInfo()`, `getUsageInfo()`, and `initializationResult()`. + +**Failure criteria and cleanup** + +- A secret/token appearing in the browser, a capability failure crashing the session, or a tab operation polluting the transcript is a failure. +- No local cleanup. Record whether Plugin reload had environment side effects. + +## CASE-16 Remote MCP, OAuth, and Elicitation + +> **Runtime:** Real SDK + a trusted remote MCP supplied by the tester | **Evidence:** Direct interface + message stream + possible model behavior | **Determinism:** Low | **Side effects:** Network and OAuth/remote-server state; may call a remote Tool + +**Prerequisites** + +- Configure a dedicated, trusted, disposable MCP server through `QODER_WEBUI_MCP_CONFIG_FILE`. +- The built-in `showcase_project` does not require OAuth or initiate elicitation, so it cannot complete this case. + +**Input and procedure** + +1. Observe connected, authentication-required, or failed status in the MCP tab. +2. If OAuth is required, open the authorization address and submit the callback URL. +3. Trigger the test server's form or URL elicitation and try accept, deny, and cancel. +4. Use Reconnect (`重连`) and verify that it affects only the current session Query. + +**Expected result** + +- Silent OAuth success shows no unnecessary action; a URL/callback flow appears only when user action is required. +- Elicitation uses a dedicated inline card and is not submitted twice after resolution. +- The browser receives only a protocol-validated authorization URL and redacted, bounded MCP status. Remote headers, subprocess environments, and OAuth tokens never reach it. +- The callback URL pasted by the user exists only in component-local input and one validated submission request. The input is cleared after success, and the callback URL does not enter a snapshot, realtime event, or diagnostic record. + +**SDK mapping and evidence** + +- `mcpServerStatus()`, `mcpAuthenticate()`, `mcpSubmitOAuthCallbackUrl()`, `Options.onElicitation`, and `ElicitationResult`. + +**Failure criteria and cleanup** + +- Loading untrusted configuration, projecting credentials to the browser, reconnecting the wrong session, or accepting an unvalidated callback is a severe failure. +- Revoke test OAuth authorization, stop the test MCP server, and remove temporary configuration and tokens. + +## CASE-17 Preview and Execute a Checkpoint + +> **Runtime:** Real SDK | **Evidence:** Direct interface | **Determinism:** Medium/high (depends on rewind capability) | **Side effects:** May revert Workspace files and/or conversation history; run only in a dedicated directory + +**Prerequisites** + +- Use a dedicated Workspace and back up important changes. +- The session is live and idle, and every Approval, AskUserQuestion, and MCP elicitation has been resolved. +- Conversation/both can be selected only when initialization capabilities support full-session rewind; otherwise test files only. + +**Input and procedure** + +1. Complete a turn containing later Assistant or Tool records. To verify file rewind, first create the dedicated file in CASE-03. +2. Click `Checkpoint` on the target user message. +3. Use Tab/Shift+Tab to verify the focus trap. Press Escape to close and return focus to the trigger, then reopen it. +4. Select files, conversation, or both and click Preview Impact (`预览影响`). +5. Inspect files, insertions, deletions, failed items/rejection reasons; confirm that nothing has been reverted yet. +6. Click Execute Checkpoint (`执行 Checkpoint`). +7. Try to reuse the old preview. Then create another preview, send a new message, and try to execute it. Both stale previews must be rejected. +8. At narrow width, verify that the page does not scroll horizontally and that focus can continue from the original message after closing. + +**Expected result** + +- Execution is unavailable without a dry run, and the preview matches scope/capability. +- Success shows `success`; partial file failure shows `partial` and failed paths. +- Conversation scope retains the target user message and reloads later records from persistent SDK history. +- Busy, expired, revision-changed, and already-consumed errors appear in the dialog without a duplicate global command failure. +- Realtime state and a per-session snapshot converge to the same final semantics. + +**SDK mapping and evidence** + +- Files: `rewindFiles(userMessageId, { dryRun })`. +- Conversation/both: `rewind(userMessageId, { scope, dryRun })` and `RewindScope`. +- The idle gate, revision binding, single-use previews, sibling invalidation, mutation fence, and `conversation.replaced` are Showcase safety policies, not UI automatically provided by SDK rewind. + +**Failure criteria and cleanup** + +- Executing without a preview, reusing a stale preview, interleaving concurrent send and rewind, or deleting browser rows without rereading history is a failure. +- Verify files and transcript, then clean up the dedicated file and session. If the rewind impact is uncertain, stop using the Workspace and restore from backup. + +## Capabilities That Are Not Evidence of a Direct UI Call + +### Task Controls + +The server's [`runtime-routes.ts`](../src/server/api/runtime-routes.ts) and [`runtime-capability-service.ts`](../src/server/sdk/runtime-capability-service.ts), and the browser's [`api-client.ts`](../src/client/transport/api-client.ts), adapt: + +- `Query.backgroundTasks(toolUseId?)` +- `Query.stopTask(taskId)` + +The current React product has no reachable Task list or control entry point. CASE-09 therefore proves model behavior and the Task message stream only; it cannot claim that the user directly called these methods from the UI. + +### Other Query Methods Not Exposed + +[`query-port.ts`](../src/server/sdk/query-port.ts) intentionally declares only the subset of SDK `Query` used by product services. Learn capabilities absent from the port through the public SDK reference and focused samples. Even when a method exists in the port, it can be described as a product feature only after it has a reachable UI, product policy, error ownership, and deterministic tests. + +## Product Feedback Template + +| Field | Example | +| --- | --- | +| Case | CASE-06 MCP Tool | +| Runtime | Real SDK / fixture | +| Result | Pass / partial pass / fail / unsupported by environment | +| Evidence level | Direct interface / message stream / model behavior | +| Actual behavior | MCP connected; the model selected the requested Tool on the second attempt | +| Determinism variance | The prompt was not followed on the first attempt, but connection and Tool events were correct | +| Side effects and cleanup | Read-only; no additional cleanup | +| Discoverability | Easy / moderate / difficult | +| State clarity | Clear / missing wait indicator / error shown in the wrong place | +| Evidence attachment | Screenshot, Raw Event type, session title | +| Requested improvement | Show the MCP Server name in the Tool row | + +During a consolidated review, focus on these questions: + +1. Which SDK capabilities belong in the ordinary product UI, and which should remain in SDK Console? +2. Can Tool, Approval, Subagent, MCP, and Task states be distinguished? +3. Does an error appear in the turn/control that owns it and explain the next action? +4. Which capabilities can be discovered only through a prompt, and do they need an explicit entry point? +5. Can UI behavior be traced quickly to an adapter in the [SDK capability map](SDK_CODE_TOUR.md#capability-map)? +6. Which local application infrastructure could be mistaken for an SDK-provided capability? + +## Developer Verification Boundary + +Manual trials do not replace deterministic regression tests, and fixture regression does not replace real-account verification. Run `npm run check` before publishing by default. Run the real smoke test only when external SDK calls are explicitly intended. See [Verification in the root README](../README.md#verification) for complete commands, deadlines, and cleanup semantics. diff --git a/typescript/web-ui-showcase/docs/SDK_CODE_TOUR.md b/typescript/web-ui-showcase/docs/SDK_CODE_TOUR.md new file mode 100644 index 0000000..cbf9dff --- /dev/null +++ b/typescript/web-ui-showcase/docs/SDK_CODE_TOUR.md @@ -0,0 +1,222 @@ +# Web UI Showcase SDK Code Tour + +This guide answers one question: **Which Qoder TypeScript Agent SDK interface powers each capability visible in the UI, and at which layer does that interface become product behavior?** + +If you have not called the SDK yet, complete [SDK Quick Start](SDK_QUICK_START.md) first. Use the [package README](../README.md) for installation, authentication, startup, and real-versus-fixture selection, and use the [Product Trial Guide](PRODUCT_TRIAL_GUIDE.md) for manual procedures. This guide does not repeat them. + +The project currently uses `@qoder-ai/qoder-agent-sdk` 1.0.21. The browser does not import the SDK. SDK Queries, callbacks, credentials, and SDK-specific types exist only in [`src/server/sdk/`](../src/server/sdk/). + +## Understand the Four Categories First + +Before reading about a capability, identify its category: + +1. **SDK-required contract:** public interfaces such as `query()` options, asynchronous `Query` iteration, `SDKUserMessage`, session state, control receipts, callbacks, the session catalog, and rewind. +2. **Showcase policy:** which methods `QueryPort` exposes, Composer defaults, capability gates, error ownership, and Checkpoint freshness rules. +3. **Product infrastructure:** Workspace canonicalization, REST/WebSocket, command correlation, parent-child event ordering, journal replay, snapshot/history consistency, mutation fences, browser redaction, and file suggestions. +4. **Optional diagnostics:** SDK Console, Hooks, and Raw Events—which are closed by default—and the explicitly opt-in real-account smoke test. + +Every flow below identifies its category. Do not describe the third or fourth category as product behavior automatically supplied by the SDK. + +## How a Message Moves Through the System + +```text +React Composer + │ 1. browser-safe command + ▼ +Fastify route ── Zod validation + │ 2. application operation + ▼ +SessionService / RuntimeCapabilityService + │ 3. policy + registry coordination + ▼ +SessionController ── QueryPort + │ 4. SDKUserMessage / Query methods + ▼ +query() / Qoder TypeScript SDK + │ 5. AsyncIterable + ▼ +message-projector ── EventJournal ── snapshot + realtime events + │ 6. browser-safe semantic state + ▼ +AppStore / React product surface +``` + +Follow an ordinary send in this order: + +1. [`api-client.ts`](../src/client/transport/api-client.ts) sends an application command; the browser does not know the `Query` type. +2. [`session-routes.ts`](../src/server/api/session-routes.ts) validates one request and delegates one operation. +3. [`session-service.ts`](../src/server/services/session-service.ts) enforces session/Workspace policy and finds the protected live controller through [`session-registry.ts`](../src/server/sdk/session-registry.ts). +4. [`input-queue.ts`](../src/server/sdk/input-queue.ts) implements application input as an `AsyncIterable`; [`session-controller.ts`](../src/server/sdk/session-controller.ts) continuously consumes the same Query. +5. [`query-factory.ts`](../src/server/sdk/query-factory.ts) is the application's only `query()` construction point. +6. [`message-projector.ts`](../src/server/sdk/message-projector.ts) turns SDK messages into semantic actions, which the controller publishes to [`event-journal.ts`](../src/server/realtime/event-journal.ts). +7. [`snapshot-service.ts`](../src/server/services/snapshot-service.ts) and [`realtime-hub.ts`](../src/server/realtime/realtime-hub.ts) provide consistent snapshots/replay; [`app-reducer.ts`](../src/client/store/app-reducer.ts) reduces application events only. + +This is the example's most reusable boundary: SDK changes stay concentrated in the adapter, while the product layer uses stable command, event, and view models. + +## The Single Query Creation Point + +[`createQueryFactory()`](../src/server/sdk/query-factory.ts) composes authentication, Workspace, session, and interaction collaborators: + +```ts +const sdkQuery = queryFn({ + prompt: input.input, + options: { + auth, + cwd: input.workspacePath, + model: input.model ?? config.model, + permissionMode: input.permissionMode ?? config.permissionMode, + enableFileCheckpointing: config.enableCheckpoints, + includePartialMessages: true, + includeHookEvents: true, + promptSuggestions: true, + canUseTool: input.interactions.canUseTool(input.getSessionId), + onElicitation: input.interactions.onElicitation(input.getSessionId), + mcpServers: input.mcpServers, + hooks: input.hooks, + }, +}); +``` + +The real implementation also conditionally passes `sessionId`, `resume`, and `forkSession` according to intent. This fragment explains the **composition**, but it depends on the Showcase's `InputQueue`, `InteractionBroker`, and configuration and cannot be copied out of the project by itself. A complete runnable example is in [SDK Quick Start](SDK_QUICK_START.md#single-query). + +### Why Use a Narrow `QueryPort` + +[`query-port.ts`](../src/server/sdk/query-port.ts) declares only the `Query` methods consumed by this product. [`adaptQuery()`](../src/server/sdk/query-port.ts) is where a real Query enters the application. It is neither a complete SDK capability catalog nor a method-by-method wrapper: + +- Production adapts a real `Query` into `QueryPort`. +- Services and controllers depend only on this application port and do not import the SDK. +- Tests inject a fake port to verify the application lifecycle without calling a real account. +- A method without a product entry point, service policy, and assembly test is not added to the port merely for completeness. + +Use the exported types and official reference for `@qoder-ai/qoder-agent-sdk` as the authority for public SDK capabilities. Do not discover them by adding unused methods to `QueryPort`. + +## Capability Map + +| Learning goal | Public SDK symbol or method | Main adapter code | Product entry point | Representative verification | +| --- | --- | --- | --- | --- | +| Create a Query | `query`, `Options`, `AuthOptions`, `qodercliAuth`, `accessTokenFromEnv` | [`query-factory.ts`](../src/server/sdk/query-factory.ts) | Session start | [`query-factory.test.ts`](../test/unit/server/sdk/query-factory.test.ts) | +| Asynchronous input and streaming output | `SDKUserMessage`, `SDKMessage`, `includePartialMessages` | [`input-queue.ts`](../src/server/sdk/input-queue.ts), [`session-controller.ts`](../src/server/sdk/session-controller.ts), [`message-projector.ts`](../src/server/sdk/message-projector.ts) | Composer, conversation | [`input-queue.test.ts`](../test/unit/server/sdk/input-queue.test.ts), [`sessions.test.ts`](../test/integration/sessions.test.ts) | +| Session state, stop, and cancel | `session_state_changed`, `interrupt()`, `cancelAsyncMessage()` | [`session-controller.ts`](../src/server/sdk/session-controller.ts) | Stop, queued messages | [`session-controller.test.ts`](../test/unit/server/sdk/session-controller.test.ts) | +| Resume, fork, and manage sessions | `resume`, `forkSession`, `listSessions`, `getSessionInfo`, `getSessionMessages`, `renameSession`, `tagSession`, `deleteSession` | [`query-factory.ts`](../src/server/sdk/query-factory.ts), [`session-catalog.ts`](../src/server/sdk/session-catalog.ts) | Session sidebar and menus | [`session-catalog.test.ts`](../test/unit/server/sdk/session-catalog.test.ts), [`restart-hydration.test.ts`](../test/integration/restart-hydration.test.ts) | +| Approval and structured questions | `CanUseTool`, `PermissionResult`, `PermissionUpdate` | [`interaction-broker.ts`](../src/server/sdk/interaction-broker.ts), [`ask-user.ts`](../src/server/sdk/ask-user.ts) | Inline Approval and AskUserQuestion | [`interaction-broker.test.ts`](../test/unit/server/sdk/interaction-broker.test.ts), [`interactions.test.ts`](../test/integration/interactions.test.ts) | +| MCP elicitation | `OnElicitation`, `ElicitationResult` | [`interaction-broker.ts`](../src/server/sdk/interaction-broker.ts) | Inline MCP form | [`mcp-elicitation-schema.test.ts`](../test/unit/shared/mcp-elicitation-schema.test.ts), [`mcp.test.ts`](../test/integration/mcp.test.ts) | +| In-process MCP Tool | `createSdkMcpServer`, `tool`, `CallToolResult` | [`demo-mcp-server.ts`](../src/server/sdk/demo-mcp-server.ts) | `showcase_project` Tool | [`demo-mcp-server.test.ts`](../test/unit/server/sdk/demo-mcp-server.test.ts) | +| MCP status, configuration, and OAuth | `McpServerConfig`, `mcpServerStatus`, `mcpAuthenticate`, `mcpSubmitOAuthCallbackUrl` | [`mcp-config.ts`](../src/server/sdk/mcp-config.ts), [`mcp-service.ts`](../src/server/sdk/mcp-service.ts) | MCP tab in SDK Console | [`mcp-config.test.ts`](../test/unit/server/sdk/mcp-config.test.ts), [`mcp.test.ts`](../test/integration/mcp.test.ts) | +| Hook lifecycle | `HookCallback`, `HookJSONOutput`, `Options["hooks"]` | [`hooks.ts`](../src/server/sdk/hooks.ts), [`message-projector.ts`](../src/server/sdk/message-projector.ts) | Hooks/Raw Events in SDK Console | [`hooks.test.ts`](../test/unit/server/sdk/hooks.test.ts) | +| Model, Permission, and Context | `getAvailableModels`, `setModel`, `setPermissionMode`, `getContextUsage` | [`runtime-capability-service.ts`](../src/server/sdk/runtime-capability-service.ts) | Composer controls | [`runtime-capability-service.test.ts`](../test/unit/server/sdk/runtime-capability-service.test.ts), [`runtime-capabilities.test.ts`](../test/integration/runtime-capabilities.test.ts) | +| Task state and server-side controls | Task messages, `backgroundTasks`, `stopTask` | [`message-projector.ts`](../src/server/sdk/message-projector.ts), [`runtime-capability-service.ts`](../src/server/sdk/runtime-capability-service.ts), [`runtime-routes.ts`](../src/server/api/runtime-routes.ts) | **No reachable Task UI currently; events are observable in Raw Events in SDK Console** | [`session-controller.test.ts`](../test/unit/server/sdk/session-controller.test.ts), [`runtime-capabilities.test.ts`](../test/integration/runtime-capabilities.test.ts) | +| Checkpoint | `rewindFiles`, `rewind`, `RewindScope` | [`checkpoint-service.ts`](../src/server/sdk/checkpoint-service.ts), [`checkpoint-dialog.tsx`](../src/client/features/conversation/checkpoint-dialog.tsx) | Checkpoint dialog on a user message | [`checkpoints.test.ts`](../test/integration/checkpoints.test.ts), [`showcase.spec.ts`](../test/e2e/showcase.spec.ts) | +| Subagent history | `listSubagents`, `getSubagentMessages` | [`session-catalog.ts`](../src/server/sdk/session-catalog.ts), [`subagent-transcript-service.ts`](../src/server/services/subagent-transcript-service.ts) | Details for an `Agent` Tool | [`session-catalog.test.ts`](../test/unit/server/sdk/session-catalog.test.ts), [`showcase.spec.ts`](../test/e2e/showcase.spec.ts) | +| Account, Credits, and extension discovery | `accountInfo`, `getUsageInfo`, `supportedCommands`, `supportedAgents`, `listPlugins`, `reloadPlugins` | [`runtime-capability-service.ts`](../src/server/sdk/runtime-capability-service.ts), [`composer-command-catalog.ts`](../src/server/sdk/composer-command-catalog.ts) | SDK Console, Composer suggestions | [`runtime-capability-service.test.ts`](../test/unit/server/sdk/runtime-capability-service.test.ts) | +| Browser-safe projection | SDK messages/diagnostics reaching the wire boundary | [`redact.ts`](../src/server/sdk/redact.ts), [`error-text-redact.ts`](../src/server/sdk/error-text-redact.ts), [`browser-projection.ts`](../src/server/sdk/browser-projection.ts) | Errors, SDK Console | [`redact.test.ts`](../test/unit/server/sdk/redact.test.ts), [`message-projector.test.ts`](../test/unit/server/sdk/message-projector.test.ts) | + +## Key Flows + +### First Message and Parent-Child Session Events + +This is **Showcase policy + product infrastructure**, not UI supplied by `query()`: + +1. The home screen lets the user edit a draft before choosing a Workspace. +2. [`session-start-service.ts`](../src/server/services/session-start-service.ts) organizes native directory selection, canonical registration, session creation, and the first send as one operation. +3. The server canonicalizes the Workspace before it becomes the SDK `cwd`; the browser submits only a Workspace id. +4. After the Query is constructed and reserved in the registry, the application publishes the parent `session.upserted` event before calling `controller.start()` to produce lifecycle, input, or conversation child events. + +Publishing the parent first is an application event-protocol requirement. It lets snapshot/realtime reducers safely ignore late events for unknown sessions. [`sessions.test.ts`](../test/integration/sessions.test.ts) covers concurrent deduplication, first-message preservation, deletion, and fatal Query replacement. + +### Asynchronous Input and Session State + +[`InputQueue`](../src/server/sdk/input-queue.ts) implements the **SDK-required `AsyncIterable` contract** and preserves `priority` and `shouldQuery`. The product Composer default of `next` / `true` is **Showcase policy**. + +- `buffered`: still in the application's local queue. +- `delivered`: yielded to the SDK transport; does not mean that a turn completed. +- The SDK may combine inputs, and a result does not identify a unique source UUID. The controller therefore tracks submitted batches conservatively and does not clear every input after an arbitrary result. +- After `session_state_changed` is observed, `idle`, `running`, and `requires_action` are authoritative. Inferring idle from a result exists only for compatibility with older runtimes that do not send state. +- `interrupt()` coordinates using `still_queued` and optional `cancelled` UUIDs. A successful `cancelAsyncMessage(uuid)` removes only the corresponding input. + +See [Long-Lived Interactive Query](SDK_QUICK_START.md#long-lived-interactive-query) for a copyable standalone example and field semantics. + +### Semantic Projection and History Recovery + +The SDK stream contains partial text, Tool lifecycle records, Tasks, Hooks, results, and control receipts. A product cannot render every frame as a separate card: + +- [`projectSdkMessage()`](../src/server/sdk/message-projector.ts) turns live messages into semantic actions. +- [`history-projector.ts`](../src/server/sdk/history-projector.ts) recovers the same final user, Assistant, and Tool semantics from public SDK session history. +- Assistant text is split at Tool boundaries, and Tool input/status/result are merged by tool-use id. +- Records with `parent_tool_use_id` that belong to Subagent internals do not enter the main transcript. Only after the parent `Agent` Tool is selected does the server read that history through the public catalog API. + +[`snapshot-service.ts`](../src/server/services/snapshot-service.ts) is **product infrastructure**. It coalesces concurrent history reads for the same session, buffers realtime mutations while loading, and uses generations so replacement/deletion invalidates stale reads. Checkpoint's `conversation.replaced` is authoritative. Live/history equivalence tests ignore transport UUIDs, timestamps, and temporary streaming state and compare only final semantics. + +### Approval, AskUserQuestion, and MCP Elicitation + +[`InteractionBroker`](../src/server/sdk/interaction-broker.ts) preserves the SDK callback Promise contract: + +1. `canUseTool` or `onElicitation` receives an SDK request. +2. The broker stores the pending resolver and publishes a browser-safe interaction view. +3. After the user responds, the application resolves it by opaque interaction id; an SDK abort signal ends the wait. +4. Once a session closes, the application cannot respond to an expired request. + +This adapter pattern is reusable. The specific inline cards, error text, and “allow once/deny” choices are product policy. + +### Hook Sources and Browser Safety + +[`createShowcaseHooks()`](../src/server/sdk/hooks.ts) produces callback observations with `source: "callback"` and `phase: "observation"`. Hook lifecycle records from the SDK message stream use `source: "sdk-event"` and retain the hook id. These are two valid evidence sources and must not be deduplicated only because they share an event name. + +Before Hook input, Raw Events, permission-denied text, and MCP metadata enter the browser, credentials are redacted and limits are applied for bytes, depth, nodes, strings, and list counts. `redactForBrowser()`, `boundedErrorText()`, and `safeDiagnosticRecord()` are reusable utilities. The browser never receives SDK objects or remote MCP secrets. + +### Task: An Adapter Does Not Imply a Product Control + +SDK Task messages are projected into stable Task state. The server's [`runtime-routes.ts`](../src/server/api/runtime-routes.ts) and [`runtime-capability-service.ts`](../src/server/sdk/runtime-capability-service.ts), and the browser's [`api-client.ts`](../src/client/transport/api-client.ts), adapt `backgroundTasks()` / `stopTask()`. + +**The current React product has no reachable Task list, background button, or stop button.** The source includes [`task-details.tsx`](../src/client/features/tasks/task-details.tsx) and API wiring, but the conversation does not expose a way to set a Task as `detailsSelection`; existing `openDetails()` calls are only for Approvals and Subagents. Asking the model in natural language to run or stop a Shell task in the background proves model behavior and the Task message stream only. It does not prove that the UI called these Query methods directly. Adding controls requires a discoverable entry point, command ownership, state feedback, and deterministic browser tests. + +### Checkpoint: Safety Beyond SDK Rewind + +The SDK provides `rewindFiles()`, `rewind()`, and `RewindScope`. The Showcase additionally enforces: + +- Only a live, idle session with no pending interaction or mutation can be previewed. +- A dry run is bound to the session, target message, scope, capability, expiration time, and transcript revision. +- A preview can be consumed only once. Starting execution invalidates other previews for the same session. +- New input, session restart/deletion, or revision drift invalidates a preview. +- Rewind runs inside the mutation fence in [`session-registry.ts`](../src/server/sdk/session-registry.ts), blocking concurrent sends and history replacement. +- After conversation rewind, the application rereads SDK history and publishes one `conversation.replaced` event instead of making the browser guess which rows to delete. + +This is **Showcase policy + product infrastructure**. It must not be described as product semantics automatically generated when `rewind()` is called. + +### `@ Files` and Allowed Directories + +`Query.addDirectories()` is an SDK call. `@ Files` is application infrastructure, not an SDK file-search API: + +- The browser waits 200 ms and cancels requests superseded by new input. +- The `AbortSignal` reaches the file scan when the HTTP client disconnects. +- The server searches the canonical Workspace first, then allowed roots in stable order; every root shares one entry budget. +- Symlinks, generated directories, and real paths outside the roots are skipped. Only paths are returned; file contents are not read. + +The corresponding code is in [`workspace-file-service.ts`](../src/server/services/workspace-file-service.ts) and [`prompt-composer.tsx`](../src/client/features/conversation/prompt-composer.tsx). + +## Common Mistakes to Avoid + +- Importing `@qoder-ai/qoder-agent-sdk` from React, API, or general-purpose services. +- Passing a browser path directly as SDK `cwd`, or allowing an existing session to switch Workspace silently. +- Creating a new Query for every user message instead of providing asynchronous input to one long-lived Query. +- Treating transport delivery as turn completion, or assuming every result corresponds to one input UUID. +- Rendering every SDK frame as one conversation row, which duplicates deltas, Tools, and recovered history. +- Returning SDK objects, Tool input, Hook payloads, MCP metadata, or raw errors directly to the browser. +- Resolving or rejecting an Approval or elicitation Promise after the session closes. +- Reusing an old Query after a fatal output or transport error. +- Publishing session-scoped child events before the parent session enters event state. +- Executing rewind from a stale Checkpoint preview, or replacing history during concurrent send/lifecycle operations. +- Treating deterministic fake tests as evidence that a real account, model, or remote MCP was verified. +- Describing a server/API adapter as an existing product control, especially for current Task controls. + +## Extending This Example + +When adding a browser-visible SDK capability: + +1. Add the smallest adapter in [`src/server/sdk/`](../src/server/sdk/) or extend the existing `QueryPort`. Only methods the application actually consumes belong in the port. +2. Define the browser-safe command, event, snapshot, or view model in [`src/shared/`](../src/shared/). +3. Implement product policy, capability gates, concurrency, and error ownership in the route/service layer. +4. Reduce the event into normalized client state and expose it through a natural, discoverable product entry point. +5. Add adapter unit tests and assembled integration tests. Add an E2E journey only when browser behavior is the acceptance subject. +6. Update this capability map and the [Product Trial Guide](PRODUCT_TRIAL_GUIDE.md), stating evidence level and side effects explicitly. + +After changing SDK imports, run `npm run check:boundary`. Before publishing, run the account-independent `npm run check`. See [`src/server/sdk/README.md`](../src/server/sdk/README.md) for directory-level maintenance rules. diff --git a/typescript/web-ui-showcase/docs/SDK_QUICK_START.md b/typescript/web-ui-showcase/docs/SDK_QUICK_START.md new file mode 100644 index 0000000..790e798 --- /dev/null +++ b/typescript/web-ui-showcase/docs/SDK_QUICK_START.md @@ -0,0 +1,393 @@ +# Qoder TypeScript Agent SDK Quick Start + +This guide covers the smallest patterns that can be used directly from the public exports of `@qoder-ai/qoder-agent-sdk`. It begins with a single Query and then shows a long-lived Query that continuously accepts user input. Web UI Showcase adds REST/WebSocket transport, state projection, concurrency control, and browser security boundaries on top of these contracts. + +The examples target the project's currently installed `@qoder-ai/qoder-agent-sdk` 1.0.21 and Node.js 22. + +## Prepare a Project + +Install the SDK in a new Node.js ESM project. `zod` is a peer dependency used when defining SDK MCP Tool schemas: + +```bash +npm install @qoder-ai/qoder-agent-sdk zod +``` + +The examples use a local `tsx` installation to run and use TypeScript plus Node types for static checking: + +```bash +npm install --save-dev tsx typescript @types/node +``` + +Your `package.json` must contain at least: + +```json +{ + "type": "module" +} +``` + +### Choose Authentication + +- If the machine is already signed in through qodercli, use `qodercliAuth()`. +- If the server environment provides a personal access token, set `QODER_PERSONAL_ACCESS_TOKEN` and use `accessTokenFromEnv()`. + +```ts +import { + accessTokenFromEnv, + qodercliAuth, +} from "@qoder-ai/qoder-agent-sdk"; + +const auth = process.env.QODER_PERSONAL_ACCESS_TOKEN + ? accessTokenFromEnv() + : qodercliAuth(); +``` + +Keep the authentication object, token, SDK Query, and callbacks inside a trusted Node.js process. Do not place them in a browser bundle, REST response, localStorage, or client-side log. + +## Single Query + +Estimated time: 5 minutes. + +Create `one-shot.ts`: + +```ts +import { qodercliAuth, query } from "@qoder-ai/qoder-agent-sdk"; + +const prompt = + process.argv.slice(2).join(" ") || "Describe the three main directories in this project."; + +const q = query({ + prompt, + options: { + auth: qodercliAuth(), + cwd: process.cwd(), + }, +}); + +try { + for await (const message of q) { + if (message.type !== "assistant") continue; + + for (const block of message.message.content) { + if (block.type === "text" && block.text !== undefined) { + process.stdout.write(block.text); + } + } + } +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; +} finally { + await q.close(); +} +``` + +Run it: + +```bash +npx tsx one-shot.ts "Read only the current directory and summarize the purpose of package.json." +``` + +This example has five essential boundaries: + +1. `query()` is the Query creation entry point. +2. The server decides `auth` and `cwd`; `cwd` is the project root in which the Agent may work. +3. A `Query` is an `AsyncIterable` and must be consumed with `for await`. +4. Do not treat every SDK message as final Assistant text. This example explicitly filters for `assistant` messages and `text` blocks. +5. Call `await q.close()` in `finally`, whether execution completes normally or exits with an error. + +For token authentication, replace only the import and `auth`: + +```ts +import { + accessTokenFromEnv, + query, +} from "@qoder-ai/qoder-agent-sdk"; + +const q = query({ + prompt: "Inspect the current project.", + options: { + auth: accessTokenFromEnv(), + cwd: process.cwd(), + }, +}); +``` + +`accessTokenFromEnv()` reads the server environment. Do not pass the token through a prompt, browser field, or log message. + +## Long-Lived Interactive Query + +Estimated time: 15 minutes. + +A chat application, WebSocket service, or queue consumer should not turn every user message into a new Query. Create an `AsyncIterable` that continuously feeds messages into one Query while a separate asynchronous task continuously consumes SDK output. + +The following `interactive.ts` is a complete terminal example. Its queue supports a single SDK consumer, which is enough to demonstrate the core contract a Web service must implement: + +```ts +import { randomUUID } from "node:crypto"; +import { createInterface } from "node:readline/promises"; +import { + qodercliAuth, + query, + type SDKUserMessage, +} from "@qoder-ai/qoder-agent-sdk"; + +type Priority = NonNullable; + +class InputQueue implements AsyncIterable { + readonly #items: SDKUserMessage[] = []; + #wake: (() => void) | undefined; + #closed = false; + + push(message: SDKUserMessage): void { + if (this.#closed) throw new Error("InputQueue is closed."); + this.#items.push(message); + this.#notify(); + } + + cancel(uuid: string): boolean { + const index = this.#items.findIndex((message) => message.uuid === uuid); + if (index === -1) return false; + this.#items.splice(index, 1); + return true; + } + + close(): void { + if (this.#closed) return; + this.#closed = true; + this.#notify(); + } + + async *[Symbol.asyncIterator](): AsyncGenerator { + while (true) { + const message = this.#items.shift(); + if (message !== undefined) { + yield message; + continue; + } + if (this.#closed) return; + await new Promise((resolve) => { + this.#wake = resolve; + }); + } + } + + #notify(): void { + const wake = this.#wake; + this.#wake = undefined; + wake?.(); + } +} + +function userMessage( + text: string, + options: { priority?: Priority; shouldQuery?: boolean } = {}, +): SDKUserMessage { + return { + type: "user", + message: { + role: "user", + content: [{ type: "text", text }], + }, + parent_tool_use_id: null, + uuid: randomUUID(), + priority: options.priority ?? "next", + shouldQuery: options.shouldQuery ?? true, + }; +} + +function parseInput(line: string): { + text: string; + priority: Priority; + shouldQuery: boolean; +} { + if (line.startsWith("/now ")) { + return { text: line.slice(5), priority: "now", shouldQuery: true }; + } + if (line.startsWith("/later ")) { + return { text: line.slice(7), priority: "later", shouldQuery: true }; + } + if (line.startsWith("/note ")) { + return { text: line.slice(6), priority: "next", shouldQuery: false }; + } + return { text: line, priority: "next", shouldQuery: true }; +} + +const input = new InputQueue(); +const terminal = createInterface({ input: process.stdin, output: process.stdout }); +const q = query({ + prompt: input, + options: { + auth: qodercliAuth(), + cwd: process.cwd(), + }, +}); + +let lastUuid: string | undefined; +let outputError: unknown; +const outputTask = (async () => { + try { + for await (const message of q) { + if ( + message.type === "system" && + message.subtype === "session_state_changed" + ) { + console.error(`\n[Session: ${message.state}]`); + } + if (message.type !== "assistant") continue; + for (const block of message.message.content) { + if (block.type === "text" && block.text !== undefined) { + process.stdout.write(block.text); + } + } + } + } catch (error) { + outputError = error; + terminal.close(); + } +})(); + +try { + console.log("Enter a message; /now, /later, and /note change the next message's semantics."); + console.log("/interrupt stops the current turn, /cancel cancels the previous message, and /exit quits.\n"); + + for await (const rawLine of terminal) { + const line = rawLine.trim(); + if (line.length === 0) continue; + if (line === "/exit") break; + + if (line === "/interrupt") { + console.dir(await q.interrupt(), { depth: null }); + continue; + } + + if (line === "/cancel") { + if (lastUuid === undefined) { + console.error("There is no message to cancel."); + continue; + } + const cancelledLocally = input.cancel(lastUuid); + const cancelled = + cancelledLocally || (await q.cancelAsyncMessage(lastUuid)); + console.error(cancelled ? "Message cancelled." : "The message can no longer be cancelled."); + if (cancelled) lastUuid = undefined; + continue; + } + + const parsed = parseInput(line); + if (parsed.text.trim().length === 0) continue; + const message = userMessage(parsed.text, parsed); + lastUuid = message.uuid; + input.push(message); + } +} finally { + terminal.close(); + input.close(); + await q.close(); + await outputTask; +} + +if (outputError !== undefined) throw outputError; +``` + +Run it: + +```bash +npx tsx interactive.ts +``` + +Try these inputs: + +```text +Summarize the current project. Read files only. +/later After the current response finishes, reply with LATER_OK only. +/now Stop the current response immediately and reply with NOW_OK only. +/note This note is added to the conversation without starting a model turn by itself. +/cancel +/interrupt +/exit +``` + +### `priority` and `shouldQuery` + +| Field | Meaning | +| --- | --- | +| `priority: "now"` | Interrupt the active response and process this input as soon as possible | +| `priority: "next"` | Default; process at the next appropriate safe point | +| `priority: "later"` | Wait for the current response to finish and the session to become ready | +| `shouldQuery: false` | Add the message to the conversation without letting that message start a model turn by itself; delivery timing still follows `priority` | + +Every asynchronous message that must be tracked or cancelled should use a `uuid` that is unique within the session. The example first tries to cancel the message in the application's local queue. If the `AsyncIterable` has already yielded the message to the SDK, it calls `cancelAsyncMessage(uuid)` instead. + +### Do Not Invent a One-Input-to-One-Result Relationship + +- Yielding a message from the application queue to the SDK proves only that the input reached the SDK transport; it does not prove that a turn completed. +- The SDK may combine multiple asynchronous inputs, and result messages do not carry a unique source-input UUID. +- `cancelAsyncMessage(uuid)` returning `true` means the queued message was cancelled. Returning `false` means it can no longer be cancelled; it does not mean the session failed. +- `interrupt()` interrupts current work without closing the Query. Coordinate the application queue using `still_queued` and the optional `cancelled` UUIDs in the receipt. +- If the runtime provides `session_state_changed`, `idle`, `running`, and `requires_action` are the primary session-lifecycle evidence. Do not clear all inputs or force the session to idle based only on an arbitrary result. + +The Showcase implementation is in [`input-queue.ts`](../src/server/sdk/input-queue.ts) and [`session-controller.ts`](../src/server/sdk/session-controller.ts). The product Composer defaults to `next` / `true`, while the application port preserves both fields in full. + +## Create, Resume, and Fork Sessions + +Choose exactly one intent when creating a Query: + +```ts +const created = query({ + prompt: input, + options: { + auth, + cwd, + sessionId: crypto.randomUUID(), + }, +}); + +const resumed = query({ + prompt: input, + options: { + auth, + cwd, + resume: existingSessionId, + }, +}); + +const forked = query({ + prompt: input, + options: { + auth, + cwd, + resume: existingSessionId, + forkSession: true, + }, +}); +``` + +- `sessionId` lets the host assign an identifier to a new session. +- `resume` restores an existing session. The host should ensure that `cwd` matches the session's Workspace. +- `forkSession: true` is used with `resume` to create a new branch from existing history; it must not overwrite the original session. +- Every Query has an independent lifecycle. When the host no longer retains a Query—for example, when closing, restarting, or deleting a session, or when switching targets under a single-active-session policy—stop accepting new commands before calling `await q.close()`. Do not continue reusing an old Query after a fatal transport or output failure. + +## Callbacks and the Browser Boundary + +A complete Web product usually also needs: + +- `canUseTool`: map the SDK permission Promise to a product Approval and respond to the SDK abort signal. +- `onElicitation`: map an MCP form/URL request to a structured product interaction. +- `hooks`: provide observation, policy, or context injection without sending Hook payloads directly to the browser. +- `mcpServers`: keep remote headers, subprocess environments, and OAuth tokens on the server. An authorization URL may be displayed according to product policy, while a user-submitted callback URL should be used for one server-side control request only and must not be written to persistent client state or diagnostic events. +- `includePartialMessages`: project incremental output into stable semantic items instead of appending every delta as a new message. + +These options are composed at the Showcase's single Query creation point, [`query-factory.ts`](../src/server/sdk/query-factory.ts). That fragment depends on the application's own `InputQueue`, `InteractionBroker`, configuration, and MCP/Hook registrations, so it is not a standalone Quick Start example. + +## SDK Contracts and Additional Showcase Work + +| Area | Public SDK contract | Showcase responsibility | +| --- | --- | --- | +| Query lifecycle | `query()`, asynchronous message iteration, `close()` | Registry, automatic availability, failure reconstruction, graceful shutdown | +| Input | `AsyncIterable`, priority, `shouldQuery`, cancel/interrupt | Local queue state, command correlation, conservative batch coordination, UI feedback | +| Session | `sessionId`, `resume`, `forkSession`, and catalog APIs | Workspace binding, concurrent-request deduplication, snapshot/history consistency | +| Browser | SDK-returned messages and callbacks | Zod wire model, semantic projection, redaction, size budgets, REST/WebSocket recovery | +| Checkpoint | `rewindFiles()`, `rewind()` | Dry-run UI, revision binding, single-use previews, mutation fence, authoritative history replacement | + +Next, read [SDK Code Tour](SDK_CODE_TOUR.md#how-a-message-moves-through-the-system) to follow one browser message through `query()`, the SDK message stream, and the client reducer. Use the [Product Trial Guide](PRODUCT_TRIAL_GUIDE.md) to verify product behavior. diff --git a/typescript/web-ui-showcase/index.html b/typescript/web-ui-showcase/index.html new file mode 100644 index 0000000..7652ab6 --- /dev/null +++ b/typescript/web-ui-showcase/index.html @@ -0,0 +1,12 @@ + + + + + + Qoder Agent SDK Web UI + + +
+ + + diff --git a/typescript/web-ui-showcase/package.json b/typescript/web-ui-showcase/package.json new file mode 100644 index 0000000..cbdeffb --- /dev/null +++ b/typescript/web-ui-showcase/package.json @@ -0,0 +1,47 @@ +{ + "name": "@qoder-samples/typescript-web-ui-showcase", + "private": true, + "type": "module", + "scripts": { + "dev": "concurrently -k -n server,client npm:dev:server npm:dev:client", + "dev:server": "node --env-file-if-exists=.env --import tsx --watch src/server/main.ts", + "dev:client": "vite", + "build": "npm run typecheck && tsc -p tsconfig.server.json && vite build", + "start": "node --env-file-if-exists=.env scripts/start-production.mjs", + "typecheck": "tsc -p tsconfig.client.json --noEmit --pretty false && tsc -p tsconfig.server.json --noEmit --pretty false && tsc -p tsconfig.test.json --noEmit --pretty false", + "test:unit": "vitest run test/unit", + "test:integration": "vitest run test/integration", + "test:e2e": "playwright test", + "test:smoke:production": "node --import tsx test/smoke/production-server-smoke.ts", + "test:smoke:real": "node --env-file-if-exists=.env --import tsx test/smoke/real-sdk-smoke.ts", + "check:boundary": "node scripts/check-sdk-import-boundary.mjs", + "check": "npm run typecheck && npm run check:boundary && npm run test:unit && npm run test:integration && npm run build && npm run test:smoke:production && npm run test:e2e" + }, + "dependencies": { + "@fastify/static": "^10.1.3", + "@fastify/websocket": "^11.0.0", + "@qoder-ai/qoder-agent-sdk": "^1.0.21", + "fastify": "^5.0.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "zod": "^4.4.3" + }, + "devDependencies": { + "@playwright/test": "^1.55.0", + "@testing-library/jest-dom": "^6.9.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.0.0", + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "@types/ws": "^8.18.0", + "@vitejs/plugin-react": "^4.7.0", + "concurrently": "^9.2.0", + "jsdom": "^29.1.1", + "tsx": "^4.22.0", + "typescript": "^5.9.0", + "vite": "^6.4.3", + "vitest": "^4.1.8", + "ws": "^8.21.0" + } +} diff --git a/typescript/web-ui-showcase/playwright.config.ts b/typescript/web-ui-showcase/playwright.config.ts new file mode 100644 index 0000000..27d534a --- /dev/null +++ b/typescript/web-ui-showcase/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "test/e2e", + fullyParallel: false, + timeout: 30_000, + use: { + baseURL: "http://127.0.0.1:4178", + browserName: "chromium", + trace: "retain-on-failure", + }, + webServer: { + command: "npm run build && ../node_modules/.bin/tsx test/e2e/fixture-server.ts", + url: "http://127.0.0.1:4178/api/health", + reuseExistingServer: false, + timeout: 120_000, + }, +}); diff --git a/typescript/web-ui-showcase/scripts/check-sdk-import-boundary.mjs b/typescript/web-ui-showcase/scripts/check-sdk-import-boundary.mjs new file mode 100644 index 0000000..ec5473a --- /dev/null +++ b/typescript/web-ui-showcase/scripts/check-sdk-import-boundary.mjs @@ -0,0 +1,39 @@ +import { readdir, readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const sourceRoot = fileURLToPath(new URL("../src/", import.meta.url)); + +async function walk(directory, prefix = "") { + const entries = await readdir(directory, { withFileTypes: true }); + const nested = await Promise.all( + entries.map(async (entry) => { + const relative = prefix ? `${prefix}/${entry.name}` : entry.name; + return entry.isDirectory() + ? walk(join(directory, entry.name), relative) + : [relative]; + }), + ); + return nested.flat(); +} + +const paths = (await walk(sourceRoot)).filter((path) => + /\.[cm]?tsx?$/.test(path), +); +const files = await Promise.all( + paths.map(async (path) => ({ + path, + source: await readFile(join(sourceRoot, path), "utf8"), + })), +); +const violations = files.filter( + ({ path, source }) => + source.includes("@qoder-ai/qoder-agent-sdk") && + !path.startsWith("server/sdk/"), +); +if (violations.length > 0) { + process.stderr.write( + `SDK import boundary violations:\n${violations.map(({ path }) => path).join("\n")}\n`, + ); + process.exitCode = 1; +} diff --git a/typescript/web-ui-showcase/scripts/start-production.mjs b/typescript/web-ui-showcase/scripts/start-production.mjs new file mode 100644 index 0000000..f92839b --- /dev/null +++ b/typescript/web-ui-showcase/scripts/start-production.mjs @@ -0,0 +1,2 @@ +process.env.NODE_ENV = "production"; +await import("../dist/node/server/main.js"); diff --git a/typescript/web-ui-showcase/src/client/app.tsx b/typescript/web-ui-showcase/src/client/app.tsx new file mode 100644 index 0000000..3b7c95c --- /dev/null +++ b/typescript/web-ui-showcase/src/client/app.tsx @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { AppShell } from "./features/layout/app-shell.js"; +import { AppErrorBoundary } from "./features/errors/app-error-boundary.js"; +import { AppStore } from "./store/app-store.js"; +import { StoreProvider } from "./store/store-context.js"; +import { ApiClient } from "./transport/api-client.js"; +import { RealtimeClient } from "./transport/realtime-client.js"; + +export function App(): JSX.Element { + const [resources] = useState(() => { + const store = new AppStore(); + return { + store, + api: new ApiClient(), + realtime: new RealtimeClient({ store }), + }; + }); + useEffect(() => { + resources.realtime.start(); + return () => resources.realtime.stop(); + }, [resources]); + return ( + + + + + + ); +} diff --git a/typescript/web-ui-showcase/src/client/assets/qoder-icon-128.png b/typescript/web-ui-showcase/src/client/assets/qoder-icon-128.png new file mode 100644 index 0000000..0f5ae4e Binary files /dev/null and b/typescript/web-ui-showcase/src/client/assets/qoder-icon-128.png differ diff --git a/typescript/web-ui-showcase/src/client/assets/qoder-icon-64.png b/typescript/web-ui-showcase/src/client/assets/qoder-icon-64.png new file mode 100644 index 0000000..f9a1e2a Binary files /dev/null and b/typescript/web-ui-showcase/src/client/assets/qoder-icon-64.png differ diff --git a/typescript/web-ui-showcase/src/client/components/safe-json.tsx b/typescript/web-ui-showcase/src/client/components/safe-json.tsx new file mode 100644 index 0000000..9512830 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/components/safe-json.tsx @@ -0,0 +1,9 @@ +export function SafeJson(props: { value: unknown }): JSX.Element { + let text: string; + try { + text = JSON.stringify(props.value, null, 2); + } catch { + text = "{\n \"error\": \"Unable to serialize this redacted event\"\n}"; + } + return
{text}
; +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/checkpoint-dialog.tsx b/typescript/web-ui-showcase/src/client/features/conversation/checkpoint-dialog.tsx new file mode 100644 index 0000000..5eec6d1 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/checkpoint-dialog.tsx @@ -0,0 +1,417 @@ +import { + useEffect, + useId, + useRef, + useState, +} from "react"; +import { createPortal } from "react-dom"; +import type { + CheckpointExecuteCommand, + CheckpointPreviewCommand, +} from "../../../shared/commands.js"; +import type { + CheckpointPreviewView, + ConversationItem, + RewindScopeView, + SessionView, +} from "../../../shared/model.js"; +import { copy } from "../../i18n/zh-cn.js"; +import { useAppState, useAppStore } from "../../store/store-context.js"; +import { useModalFocus } from "../layout/modal-focus.js"; + +type UserItem = Extract; +type Accepted = { commandId: string }; + +export type CheckpointApi = { + previewCheckpoint( + sessionId: string, + input: CheckpointPreviewCommand, + ): Promise; + executeCheckpoint( + sessionId: string, + input: CheckpointExecuteCommand, + ): Promise; +}; + +type RequestPhase = "idle" | "previewing" | "executing"; +const maximumTimerDelayMs = 2_147_483_647; + +function latestMatchingPreview( + previews: CheckpointPreviewView[], + target: UserItem, + scope: RewindScopeView, +): CheckpointPreviewView | undefined { + return [...previews].reverse().find( + (preview) => + preview.sessionId === target.sessionId && + preview.userMessageId === target.id && + preview.scope === scope, + ); +} + +export function CheckpointDialog(props: { + api: CheckpointApi; + session: SessionView; + target: UserItem; + returnFocus: HTMLElement | null; + onClose(): void; +}): JSX.Element { + const state = useAppState(); + const store = useAppStore(); + const titleId = useId(); + const descriptionId = useId(); + const dialog = useRef(null); + const firstScope = useRef(null); + const [scope, setScope] = useState("files"); + const [phase, setPhase] = useState("idle"); + const [previewCommandId, setPreviewCommandId] = useState(null); + const [executeCommandId, setExecuteCommandId] = useState(null); + const [executionPreviewId, setExecutionPreviewId] = useState(null); + const [previewRequestBaselineId, setPreviewRequestBaselineId] = useState< + string | null | undefined + >(undefined); + const [submissionError, setSubmissionError] = useState(null); + const [clock, setClock] = useState(() => Date.now()); + const supportsFullRewind = + props.session.capabilities?.includes("session_rewind_v1") === true; + const sessionBusy = props.session.phase !== "idle" || props.session.awaitingUser; + const previews = state.checkpointPreviewIds.flatMap((id) => { + const preview = state.checkpointPreviews[id]; + return preview === undefined ? [] : [preview]; + }); + const matchingPreview = latestMatchingPreview(previews, props.target, scope); + const preview = previewRequestBaselineId !== undefined && + (matchingPreview === undefined || + matchingPreview.id === previewRequestBaselineId) + ? undefined + : matchingPreview; + const completion = executionPreviewId === null + ? undefined + : state.checkpointCompletions[executionPreviewId]; + const commandFailure = [...state.commandFailures].reverse().find( + (failure) => + failure.commandId === previewCommandId || + failure.commandId === executeCommandId, + ); + const expired = preview !== undefined && + clock >= new Date(preview.expiresAt).getTime(); + const busy = phase !== "idle"; + + useModalFocus({ + open: true, + dialogRef: dialog, + initialFocusRef: firstScope, + returnFocus: props.returnFocus, + onClose: props.onClose, + }); + + useEffect(() => { + if (preview === undefined) return; + const expiresAt = new Date(preview.expiresAt).getTime(); + const timeout = window.setTimeout( + () => setClock(Date.now()), + Math.min( + Math.max(0, expiresAt - Date.now()) + 10, + maximumTimerDelayMs, + ), + ); + return () => window.clearTimeout(timeout); + }, [preview]); + + useEffect(() => { + if (phase === "previewing" && preview !== undefined) { + setPreviewRequestBaselineId(undefined); + setPhase("idle"); + } + }, [phase, preview]); + + useEffect(() => { + if (commandFailure === undefined) return; + setPhase("idle"); + setSubmissionError(commandFailure.error.message); + if (commandFailure.commandId === executeCommandId) { + setExecutionPreviewId(null); + } + if (commandFailure.commandId !== undefined) { + store.dismissCommandFailure(commandFailure.commandId); + } + }, [commandFailure, executeCommandId, store]); + + useEffect(() => { + if (completion !== undefined) { + setPhase("idle"); + } + }, [completion]); + + const selectScope = (nextScope: RewindScopeView): void => { + setScope(nextScope); + setSubmissionError(null); + setPreviewCommandId(null); + setExecuteCommandId(null); + setExecutionPreviewId(null); + setPreviewRequestBaselineId(undefined); + setClock(Date.now()); + }; + + const requestPreview = (): void => { + if (busy || sessionBusy) return; + setPhase("previewing"); + setSubmissionError(null); + setPreviewCommandId(null); + setExecuteCommandId(null); + setExecutionPreviewId(null); + setPreviewRequestBaselineId(matchingPreview?.id ?? null); + void props.api.previewCheckpoint(props.session.id, { + userMessageId: props.target.id, + scope, + }).then( + (accepted) => { + setPreviewCommandId(accepted.commandId); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "checkpoint-preview", + sessionId: props.session.id, + }); + }, + (error: unknown) => { + setPhase("idle"); + setSubmissionError( + error instanceof Error ? error.message : copy.checkpoint.requestFailed, + ); + }, + ); + }; + + const execute = (): void => { + if ( + busy || + sessionBusy || + preview === undefined || + expired || + !preview.canRewind || + preview.status !== "ready" + ) { + return; + } + setPhase("executing"); + setSubmissionError(null); + setExecuteCommandId(null); + setExecutionPreviewId(preview.id); + void props.api.executeCheckpoint(props.session.id, { + previewId: preview.id, + userMessageId: props.target.id, + scope, + }).then( + (accepted) => { + setExecuteCommandId(accepted.commandId); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "checkpoint-execute", + sessionId: props.session.id, + }); + }, + (error: unknown) => { + setPhase("idle"); + setExecutionPreviewId(null); + setSubmissionError( + error instanceof Error ? error.message : copy.checkpoint.requestFailed, + ); + }, + ); + }; + + const failureMessage = submissionError ?? commandFailure?.error.message; + const visibleFiles = preview?.filesChanged.slice(0, 20) ?? []; + + return createPortal( +
+
+

{copy.checkpoint.dialogTitle}

+

{copy.checkpoint.description}

+ +
+ {copy.checkpoint.scope} + + + +
+ {supportsFullRewind ? null : ( +

{copy.checkpoint.fullRewindUnavailable}

+ )} + {sessionBusy ? ( +

+ {copy.checkpoint.sessionBusy} +

+ ) : null} + + {phase === "previewing" ? ( +

+ {copy.checkpoint.previewing} +

+ ) : null} + {phase === "executing" && completion === undefined ? ( +

+ {copy.checkpoint.executing} +

+ ) : null} + + {preview === undefined || phase === "executing" ? null : ( +
+

{copy.checkpoint.impact}

+ {preview.canRewind ? ( + <> +
+
+
{copy.checkpoint.changedFiles}
+
{preview.filesChanged.length}
+
+
+
{copy.checkpoint.insertions}
+
+{preview.insertions}
+
+
+
{copy.checkpoint.deletions}
+
−{preview.deletions}
+
+
+ {visibleFiles.length === 0 ? ( +

{copy.checkpoint.noChangedFiles}

+ ) : ( +
    + {visibleFiles.map((file) =>
  • {file}
  • )} + {preview.filesChanged.length <= visibleFiles.length ? null : ( +
  • 另有 {preview.filesChanged.length - visibleFiles.length} 个文件
  • + )} +
+ )} + + ) : ( +

+ {preview.error?.message ?? copy.checkpoint.rejected} +

+ )} + {expired ? ( +

{copy.checkpoint.expired}

+ ) : null} + {preview.failedFiles.length === 0 ? null : ( +
+ {copy.checkpoint.failedFiles} +
    + {preview.failedFiles.map((failure) => ( +
  • {failure.path}: {failure.error}
  • + ))} +
+
+ )} +
+ )} + + {completion === undefined ? null : ( +
+ + {completion.status === "partial" + ? copy.checkpoint.partial + : copy.checkpoint.completed} + + {completion.failedFiles.length === 0 ? null : ( +
    + {completion.failedFiles.map((file) =>
  • {file}
  • )} +
+ )} +
+ )} + + {failureMessage === undefined || failureMessage === null ? null : ( +

{failureMessage}

+ )} + +
+ + {completion !== undefined ? null : preview === undefined || expired ? ( + + ) : ( + <> + + + + )} +
+
+
, + document.body, + ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/composer-control-menu.tsx b/typescript/web-ui-showcase/src/client/features/conversation/composer-control-menu.tsx new file mode 100644 index 0000000..66861d5 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/composer-control-menu.tsx @@ -0,0 +1,180 @@ +import { useEffect, useState, type Ref } from "react"; +import type { SelectablePermissionMode } from "../../../shared/commands.js"; +import type { SessionRuntimeView } from "../../../shared/model.js"; +import { + runtimeRefreshCapability, + type RuntimeCapabilityId, +} from "../../../shared/errors.js"; +import type { ContextSummary } from "./context-summary.js"; +import { ModelPicker } from "../runtime/model-picker.js"; +import { PermissionPicker } from "../runtime/permission-picker.js"; + +export type RuntimeControlFailure = { + commandId?: string; + message: string; + dismiss?(): void; +}; + +export type RuntimeControl = "model" | "permission" | "mcp"; + +export function runtimeControlReason( + runtime: SessionRuntimeView | undefined, + control: RuntimeControl, +): string | undefined { + const capability: RuntimeCapabilityId = + control === "model" + ? "models" + : control === "permission" + ? "permission" + : "mcp"; + return runtime?.errors.find((error) => + runtimeRefreshCapability(error) === capability + )?.message; +} + +/** Session-scoped controls available from the shared Composer. */ +export function ComposerControlMenu(props: { + context: ContextSummary | null; + runtime?: SessionRuntimeView; + modelRef: Ref; + permissionRef: Ref; + setModel(model?: string): Promise<{ commandId: string }>; + setPermissionMode( + mode: SelectablePermissionMode, + ): Promise<{ commandId: string }>; + modelFailure?: RuntimeControlFailure; + permissionFailure?: RuntimeControlFailure; +}): JSX.Element { + const modelReason = runtimeControlReason(props.runtime, "model"); + const permissionReason = runtimeControlReason(props.runtime, "permission"); + const [pendingModel, setPendingModel] = useState<{ + commandId: string | null; + requested: string | null; + } | null>(null); + const [pendingPermission, setPendingPermission] = useState<{ + commandId: string | null; + requested: SelectablePermissionMode; + } | null>(null); + const [submissionError, setSubmissionError] = useState(null); + const runtime = props.runtime; + + useEffect(() => { + if ( + pendingModel !== null && + (runtime?.currentModel === pendingModel.requested || + (pendingModel.commandId !== null && + props.modelFailure?.commandId === pendingModel.commandId)) + ) { + setPendingModel(null); + } + }, [pendingModel, props.modelFailure?.commandId, runtime?.currentModel]); + + useEffect(() => { + if ( + pendingPermission !== null && + (runtime?.currentPermissionMode === pendingPermission.requested || + (pendingPermission.commandId !== null && + props.permissionFailure?.commandId === pendingPermission.commandId)) + ) { + setPendingPermission(null); + } + }, [ + pendingPermission, + props.permissionFailure?.commandId, + runtime?.currentPermissionMode, + ]); + + async function setModel(model?: string): Promise<{ commandId: string }> { + const requested = model ?? null; + props.modelFailure?.dismiss?.(); + setSubmissionError(null); + setPendingModel({ commandId: null, requested }); + try { + const accepted = await props.setModel(model); + setPendingModel((current) => + current?.requested === requested + ? { commandId: accepted.commandId, requested } + : current); + return accepted; + } catch (error) { + setPendingModel(null); + setSubmissionError("无法提交 Model 设置,请重试。"); + throw error; + } + } + + async function setPermissionMode( + mode: SelectablePermissionMode, + ): Promise<{ commandId: string }> { + props.permissionFailure?.dismiss?.(); + setSubmissionError(null); + setPendingPermission({ commandId: null, requested: mode }); + try { + const accepted = await props.setPermissionMode(mode); + setPendingPermission((current) => + current?.requested === mode + ? { commandId: accepted.commandId, requested: mode } + : current); + return accepted; + } catch (error) { + setPendingPermission(null); + setSubmissionError("无法提交 Permission Mode 设置,请重试。"); + throw error; + } + } + + return ( +
+
+ + + {props.context === null ? null : ( + + {props.context.label} + + )} +
+ {submissionError === null ? null : ( +

{submissionError}

+ )} + {props.modelFailure === undefined && + props.permissionFailure === undefined ? null : ( +

+ {props.modelFailure?.message ?? props.permissionFailure?.message} + {(props.modelFailure ?? props.permissionFailure)?.dismiss === undefined + ? null + : ( + + )} +

+ )} +
+ ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/composer-drafts.ts b/typescript/web-ui-showcase/src/client/features/conversation/composer-drafts.ts new file mode 100644 index 0000000..007f779 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/composer-drafts.ts @@ -0,0 +1,23 @@ +/** Draft storage shared by the resident Home and Session Composer variants. */ +export class ComposerDrafts { + readonly #values = new Map(); + + read(key: string): string { + return this.#values.get(key) ?? ""; + } + + write(key: string, value: string): void { + this.#values.set(key, value); + } + + clear(key: string): void { + this.#values.delete(key); + } + + retain(keys: readonly string[]): void { + const retained = new Set(keys); + for (const key of this.#values.keys()) { + if (!retained.has(key)) this.#values.delete(key); + } + } +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestion-list.tsx b/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestion-list.tsx new file mode 100644 index 0000000..eeb6cfc --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestion-list.tsx @@ -0,0 +1,66 @@ +import { useLayoutEffect, useRef } from "react"; +import type { ComposerSuggestion } from "./composer-suggestions.js"; + +export function suggestionOptionId(listboxId: string, index: number): string { + return `${listboxId}-option-${index}`; +} + +export function ComposerSuggestionList(props: { + id: string; + label: string; + items: ComposerSuggestion[]; + activeIndex: number; + statusMessage?: string; + truncated?: boolean; + truncatedMessage?: string; + onHover: (index: number) => void; + onSelect: (suggestion: ComposerSuggestion) => void; +}): JSX.Element { + const optionRefs = useRef(new Map()); + const activeId = props.items[props.activeIndex]?.id; + useLayoutEffect(() => { + if (activeId === undefined) return; + optionRefs.current.get(activeId)?.scrollIntoView?.({ block: "nearest" }); + }, [activeId]); + + return ( +
+ {props.items.map((item, index) => ( + + ))} + {props.statusMessage === undefined ? null : ( +

{props.statusMessage}

+ )} + {props.truncated === true && props.truncatedMessage !== undefined ? ( + {props.truncatedMessage} + ) : null} +
+ ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestions.ts b/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestions.ts new file mode 100644 index 0000000..fd3eb9d --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/composer-suggestions.ts @@ -0,0 +1,168 @@ +import type { ComposerCommandView } from "../../../shared/model.js"; +import type { WorkspaceFileItem } from "../../../shared/workspace-files.js"; + +export type ActiveSuggestionQuery = + | { kind: "command"; start: number; end: number; query: string } + | { + kind: "file"; + start: number; + end: number; + query: string; + quoted: boolean; + }; + +export type ComposerSuggestion = + | { kind: "command"; id: string; command: ComposerCommandView } + | ({ kind: "file"; id: string } & WorkspaceFileItem); + +export type ModelOption = { value: string; label: string }; + +/** Returns distinct, non-empty suggestions in SDK order. */ +export function normalizePromptSuggestions( + suggestions: readonly string[], +): string[] { + const normalized = new Set(); + for (const suggestion of suggestions) { + for (const line of suggestion.split(/\r?\n/u)) { + const value = line.trim(); + if (value.length > 0) normalized.add(value); + } + } + return [...normalized]; +} + +export function parseSuggestionQuery( + text: string, + cursor: number, +): ActiveSuggestionQuery | null { + if (cursor < 0 || cursor > text.length) return null; + const prefix = text.slice(0, cursor); + const firstNonWhitespace = text.search(/\S/u); + if ( + firstNonWhitespace >= 0 && + firstNonWhitespace < cursor && + text[firstNonWhitespace] === "/" + ) { + const commandToken = text.slice(firstNonWhitespace + 1, cursor); + if (!/\s/u.test(commandToken)) { + return { + kind: "command", + start: firstNonWhitespace, + end: cursor, + query: commandToken, + }; + } + } + + let quotedStart = prefix.lastIndexOf('@"'); + while (quotedStart >= 0) { + const startsToken = + quotedStart === 0 || /\s/u.test(prefix[quotedStart - 1] ?? ""); + const quotedQuery = prefix.slice(quotedStart + 2); + if (startsToken && !quotedQuery.includes('"')) { + return { + kind: "file", + start: quotedStart, + end: cursor, + query: quotedQuery, + quoted: true, + }; + } + quotedStart = prefix.lastIndexOf('@"', quotedStart - 1); + } + + let tokenStart = cursor; + while (tokenStart > 0 && !/\s/u.test(text[tokenStart - 1] ?? "")) { + tokenStart -= 1; + } + const token = text.slice(tokenStart, cursor); + if (!token.startsWith("@") || token.includes('"')) return null; + return { + kind: "file", + start: tokenStart, + end: cursor, + query: token.slice(1), + quoted: false, + }; +} + +export function filterCommandSuggestions( + commands: ComposerCommandView[], + query: string, +): ComposerSuggestion[] { + const normalizedQuery = query.toLocaleLowerCase(); + return commands + .filter((command) => + command.name.toLocaleLowerCase().startsWith(normalizedQuery), + ) + .sort((left, right) => left.name.localeCompare(right.name)) + .map((command) => ({ + kind: "command" as const, + id: `command:${command.name}`, + command, + })); +} + +export function readModelOptions( + models: readonly Record[], +): ModelOption[] { + const options = new Map(); + for (const model of models) { + const value = ["value", "id", "model", "name"] + .map((key) => model[key]) + .find( + (candidate): candidate is string => + typeof candidate === "string" && candidate.length > 0, + ); + if (value === undefined || options.has(value)) continue; + const label = ["displayName", "name", "label", "id", "model", "value"] + .map((key) => model[key]) + .find( + (candidate): candidate is string => + typeof candidate === "string" && candidate.length > 0, + ); + options.set(value, { value, label: label ?? value }); + } + return [...options.values()].sort((left, right) => + left.label.localeCompare(right.label), + ); +} + +export function resolveCompletedCommand( + text: string, + commands: readonly ComposerCommandView[], +): { command: ComposerCommandView; argument: string } | undefined { + const match = /^\/([^\s]+)(?:\s+(.*))?$/u.exec(text.trim()); + if (match === null) return undefined; + const command = commands.find((candidate) => candidate.name === match[1]); + return command === undefined + ? undefined + : { command, argument: (match[2] ?? "").trim() }; +} + +export function applySuggestion( + text: string, + _cursor: number, + query: ActiveSuggestionQuery, + suggestion: ComposerSuggestion, +): { text: string; cursor: number } { + let replacement: string; + switch (suggestion.kind) { + case "command": + replacement = `/${suggestion.command.name}${ + suggestion.command.argumentHint.trim().length > 0 ? " " : "" + }`; + break; + case "file": + replacement = `${ + /\s/u.test(suggestion.mention) + ? `@"${suggestion.mention}"` + : `@${suggestion.mention}` + } `; + break; + } + return { + text: `${text.slice(0, query.start)}${replacement}${text.slice(query.end)}`, + cursor: query.start + replacement.length, + }; +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/context-summary.ts b/typescript/web-ui-showcase/src/client/features/conversation/context-summary.ts new file mode 100644 index 0000000..5084fd7 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/context-summary.ts @@ -0,0 +1,64 @@ +export type ContextSummary = { + label: string; + title: string; +}; + +function record(value: unknown): Record | undefined { + return typeof value === "object" && value !== null && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +function numberAt( + value: Record | undefined, + key: string, +): number | undefined { + const candidate = value?.[key]; + return typeof candidate === "number" && Number.isFinite(candidate) + ? candidate + : undefined; +} + +function percentageLabel(value: number): string { + const percentage = value >= 0 && value <= 1 ? value * 100 : value; + return `${Math.round(Math.min(100, Math.max(0, percentage)))}%`; +} + +function formatTokens(value: number): string { + return Math.max(0, Math.round(value)).toLocaleString("en-US"); +} + +/** Summarizes old and current SDK Context responses for the Composer. */ +export function readContextSummary( + status: "loading" | "ready" | "unsupported" | undefined, + context: Record | undefined, +): ContextSummary | null { + if (status !== "ready") return null; + + const contextWindow = record(context?.contextWindow); + const percentage = + numberAt(context, "percentage") ?? + numberAt(context, "percent") ?? + numberAt(context, "usedPercentage") ?? + numberAt(contextWindow, "usedPercentage"); + const usedTokens = + numberAt(context, "totalTokens") ?? + numberAt(context, "usedTokens") ?? + numberAt(contextWindow, "usedTokens"); + const maxTokens = + numberAt(context, "maxTokens") ?? + numberAt(context, "sizeTokens") ?? + numberAt(contextWindow, "sizeTokens"); + + if (maxTokens === undefined || maxTokens <= 0 || percentage === undefined) { + return null; + } + + return { + label: `Context ${percentageLabel(percentage)}`, + title: + usedTokens === undefined + ? `当前 Session 的 Context 上限为 ${formatTokens(maxTokens)} tokens。` + : `已使用 ${formatTokens(usedTokens)} / ${formatTokens(maxTokens)} tokens`, + }; +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/conversation-panel.tsx b/typescript/web-ui-showcase/src/client/features/conversation/conversation-panel.tsx new file mode 100644 index 0000000..bfaf051 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/conversation-panel.tsx @@ -0,0 +1,106 @@ +import { useEffect, useState } from "react"; +import type { + CheckpointExecuteCommand, + CheckpointPreviewCommand, + InteractionResponse, + SendMessageInput, +} from "../../../shared/commands.js"; +import type { ConversationItem } from "../../../shared/model.js"; +import type { WorkspaceFileSearchResult } from "../../../shared/workspace-files.js"; +import type { SubagentTranscriptResponse } from "../../../shared/subagents.js"; +import { useAppState, useAppStore } from "../../store/store-context.js"; +import { InteractionCard } from "../interactions/interaction-card.js"; +import { CheckpointDialog } from "./checkpoint-dialog.js"; +import { MessageList } from "./message-list.js"; + +type Accepted = { commandId: string }; +type UserMessage = Extract; +type CheckpointSelection = { + item: UserMessage; + trigger: HTMLButtonElement; +}; + +export type ConversationApi = { + sendMessage(sessionId: string, input: SendMessageInput): Promise; + cancelMessage(sessionId: string, uuid: string): Promise; + respondToInteraction(id: string, response: InteractionResponse): Promise; + stopTask(sessionId: string, taskId: string): Promise; + backgroundTasks(sessionId: string, toolUseId?: string): Promise; + interruptSession(sessionId: string): Promise; + refreshContext(sessionId: string): Promise; + previewCheckpoint( + sessionId: string, + input: CheckpointPreviewCommand, + ): Promise; + executeCheckpoint( + sessionId: string, + input: CheckpointExecuteCommand, + ): Promise; + searchWorkspaceFiles( + sessionId: string, + query: string, + signal?: AbortSignal, + ): Promise; + getSubagentTranscript( + sessionId: string, + toolUseId: string, + signal?: AbortSignal, + ): Promise; +}; + +export function ConversationPanel(props: { + api: ConversationApi; +}): JSX.Element { + const state = useAppState(); + const store = useAppStore(); + const sessionId = state.selectedSessionId; + const [checkpoint, setCheckpoint] = useState(); + useEffect(() => { + setCheckpoint(undefined); + }, [sessionId]); + const session = sessionId === null ? undefined : state.sessions[sessionId]; + if (session === undefined) { + return <>; + } + const messages = state.messages[session.id] ?? []; + const interactions = state.interactionIds.flatMap((id) => { + const interaction = state.interactions[id]; + return interaction?.sessionId === session.id ? [interaction] : []; + }); + const checkpointAvailable = + session.checkpointEnabled !== false && + session.phase === "idle" && + interactions.length === 0; + return ( + <> + store.openDetails({ + kind: "subagent", + sessionId: session.id, + toolUseId: item.toolUseId, + })} + {...(!checkpointAvailable + ? {} + : { + onCheckpoint: (item: UserMessage, trigger: HTMLButtonElement) => + setCheckpoint({ item, trigger }), + })} + > +
+ {interactions.map((interaction) => props.api.respondToInteraction(id, response)} onSelect={(interactionId) => store.openDetails({ kind: "approval", sessionId: session.id, interactionId })} />)} +
+
+ {checkpoint === undefined ? null : ( + setCheckpoint(undefined)} + /> + )} + + ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/conversation-root.tsx b/typescript/web-ui-showcase/src/client/features/conversation/conversation-root.tsx new file mode 100644 index 0000000..4a9107f --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/conversation-root.tsx @@ -0,0 +1,267 @@ +import { useCallback, useEffect, useState } from "react"; +import type { + SelectablePermissionMode, + SessionStarted, + StartSessionCommand, +} from "../../../shared/commands.js"; +import type { WorkspaceView } from "../../../shared/model.js"; +import { copy } from "../../i18n/zh-cn.js"; +import qoderIconUrl from "../../assets/qoder-icon-128.png"; +import { useAppState, useAppStore } from "../../store/store-context.js"; +import { findCommandFailure } from "../../store/command-ownership.js"; +import { ComposerDrafts } from "./composer-drafts.js"; +import { CommandFailureNotice } from "../errors/command-failure-notice.js"; +import { + ConversationPanel, + type ConversationApi, +} from "./conversation-panel.js"; +import { PromptComposer, type ComposerTarget } from "./prompt-composer.js"; + +type ConversationRootApi = ConversationApi & { + startSession(input: StartSessionCommand): Promise; + setModel(sessionId: string, model?: string): Promise<{ commandId: string }>; + setPermissionMode( + sessionId: string, + mode: SelectablePermissionMode, + ): Promise<{ commandId: string }>; +}; + +export function ConversationRoot(props: { + api: ConversationRootApi; + workspaces: WorkspaceView[]; + autoResumingSessionIds: ReadonlySet; + ensureFailedSessionIds: ReadonlySet; + onAccepted: (label: string, command: { commandId: string }) => void; + realtime: { selectSession(sessionId: string | null): void }; +}): JSX.Element { + const state = useAppState(); + const store = useAppStore(); + const [drafts] = useState(() => new ComposerDrafts()); + useEffect(() => { + drafts.retain(["home", ...state.sessionIds]); + }, [drafts, state.sessionIds]); + const sessionId = state.selectedSessionId; + const session = sessionId === null ? undefined : state.sessions[sessionId]; + const pinnedHomeWorkspace = state.homeWorkspaceId === null + ? undefined + : props.workspaces.find( + (candidate) => candidate.id === state.homeWorkspaceId, + ); + const workspace = pinnedHomeWorkspace ?? [...props.workspaces].sort( + (left, right) => + new Date(right.updatedAt).getTime() - new Date(left.updatedAt).getTime(), + )[0]; + const sessionAutoResuming = + session === undefined + ? false + : props.autoResumingSessionIds.has(session.id); + const settling = + sessionId !== null && (session === undefined || sessionAutoResuming); + const modelFailure = session === undefined + ? undefined + : findCommandFailure(state, { + surface: "runtime", + control: "model", + sessionId: session.id, + }); + const permissionFailure = session === undefined + ? undefined + : findCommandFailure(state, { + surface: "runtime", + control: "permission", + sessionId: session.id, + }); + const availabilityMessage = + session?.phase === "restorable" + ? props.ensureFailedSessionIds.has(session.id) + ? copy.session.ensureFailed + : session.failure?.message + : undefined; + const phase = + sessionId === null + ? "hero" + : session === undefined || sessionAutoResuming + ? "settling" + : availabilityMessage !== undefined + ? "unavailable" + : "active"; + const queued = + session === undefined + ? [] + : state.queuedInputIds.flatMap((id) => { + const item = state.queuedInputs[id]; + return item?.sessionId === session.id ? [item] : []; + }); + const searchWorkspaceFiles = useCallback( + (sessionId: string, query: string, signal?: AbortSignal) => + props.api.searchWorkspaceFiles(sessionId, query, signal), + [props.api], + ); + const target: ComposerTarget = + session === undefined + ? { + kind: "home", + workspaceId: workspace?.id ?? null, + start: async (text) => { + const started = await props.api.startSession({ + ...(workspace === undefined ? {} : { workspaceId: workspace.id }), + text, + }); + props.realtime.selectSession(started.sessionId); + }, + } + : { + kind: "session", + session, + ...(state.runtime[session.id] === undefined + ? {} + : { runtime: state.runtime[session.id] }), + send: async (text) => { + const accepted = await props.api.sendMessage(session.id, { text }); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "send", + sessionId: session.id, + }); + }, + stop: async () => { + const accepted = await props.api.interruptSession(session.id); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "stop", + sessionId: session.id, + }); + }, + setModel: async (model) => { + const accepted = await props.api.setModel(session.id, model); + store.registerCommand(accepted.commandId, { + surface: "runtime", + control: "model", + sessionId: session.id, + }); + return accepted; + }, + setPermissionMode: async (mode) => { + const accepted = await props.api.setPermissionMode(session.id, mode); + store.registerCommand(accepted.commandId, { + surface: "runtime", + control: "permission", + sessionId: session.id, + }); + return accepted; + }, + openMcp: () => store.openSdkConsole("mcp"), + ...(modelFailure === undefined + ? {} + : { + modelFailure: { + ...(modelFailure.commandId === undefined + ? {} + : { + commandId: modelFailure.commandId, + dismiss: () => { + if (modelFailure.commandId !== undefined) { + store.dismissCommandFailure(modelFailure.commandId); + } + }, + }), + message: modelFailure.error.message, + }, + }), + ...(permissionFailure === undefined + ? {} + : { + permissionFailure: { + ...(permissionFailure.commandId === undefined + ? {} + : { + commandId: permissionFailure.commandId, + dismiss: () => { + if (permissionFailure.commandId !== undefined) { + store.dismissCommandFailure( + permissionFailure.commandId, + ); + } + }, + }), + message: permissionFailure.error.message, + }, + }), + refreshContext: async () => { + const accepted = await props.api.refreshContext(session.id); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "context", + sessionId: session.id, + }); + props.onAccepted("Context 刷新请求已接受", accepted); + }, + }; + + return ( +
+ {phase === "hero" ? ( +
+ +

{copy.home.title}

+

{copy.home.subtitle}

+

+ {workspace === undefined + ? copy.home.noWorkspace + : `${copy.home.recentWorkspace}:${workspace.displayName}`} +

+
+ ) : phase === "settling" ? ( +
+

{copy.composer.restoringPlaceholder}

+
+ ) : phase === "unavailable" ? ( +
+

{availabilityMessage ?? copy.common.unavailable}

+
+ ) : ( + + )} + {session === undefined ? null : ( + ({ + surface: "conversation" as const, + control, + sessionId: session.id, + }))} /> + )} + { + const accepted = await props.api.cancelMessage( + session.id, + uuid, + ); + store.registerCommand(accepted.commandId, { + surface: "conversation", + control: "cancel", + sessionId: session.id, + }); + return accepted; + }, + })} + searchWorkspaceFiles={searchWorkspaceFiles} + /> +
+ ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/message-item.tsx b/typescript/web-ui-showcase/src/client/features/conversation/message-item.tsx new file mode 100644 index 0000000..f5d3234 --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/message-item.tsx @@ -0,0 +1,60 @@ +import type { ConversationItem } from "../../../shared/model.js"; +import { copy } from "../../i18n/zh-cn.js"; +import { ToolCard } from "./tool-card.js"; + +function assertNever(value: never): never { + throw new Error(`Unknown conversation item: ${JSON.stringify(value)}`); +} + +export function MessageItem(props: { + item: ConversationItem; + onSelectAgent?: ( + item: Extract, + ) => void; + onCheckpoint?: ( + item: Extract, + trigger: HTMLButtonElement, + ) => void; +}): JSX.Element | null { + const { item } = props; + switch (item.kind) { + case "user": + return ( +
+

{item.text}

+ {props.onCheckpoint === undefined ? null : ( +
+ +
+ )} +
+ ); + case "assistant": { + const streaming = item.status === "streaming"; + return

{item.text}

{streaming ? : null}{item.status === "interrupted" ? {copy.conversation.interrupted} : null}{item.status === "failed" ? {copy.conversation.failed} : null}
; + } + case "tool": + return ( + + ); + case "progress": + return
{item.label}{item.detail === undefined ? null :

{item.detail}

}
; + case "error": + return
{item.error.code}

{item.error.message}

; + default: + return assertNever(item); + } +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/message-list.tsx b/typescript/web-ui-showcase/src/client/features/conversation/message-list.tsx new file mode 100644 index 0000000..f9830fa --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/message-list.tsx @@ -0,0 +1,58 @@ +import { useLayoutEffect, useRef, type ReactNode } from "react"; +import type { ConversationItem } from "../../../shared/model.js"; +import { EventBoundary } from "../errors/event-boundary.js"; +import { MessageItem } from "./message-item.js"; + +export function MessageList(props: { + sessionId: string; + items: ConversationItem[]; + children?: ReactNode; + onSelectAgent?: ( + item: Extract, + ) => void; + onCheckpoint?: ( + item: Extract, + trigger: HTMLButtonElement, + ) => void; +}): JSX.Element { + const list = useRef(null); + const followLatest = useRef(true); + const previousSessionId = useRef(props.sessionId); + useLayoutEffect(() => { + if (previousSessionId.current !== props.sessionId) { + previousSessionId.current = props.sessionId; + followLatest.current = true; + } + const element = list.current; + if (element !== null && followLatest.current) { + element.scrollTop = element.scrollHeight; + } + }, [props.children, props.items, props.sessionId]); + return ( +
{ + const element = event.currentTarget; + followLatest.current = + element.scrollHeight - element.scrollTop - element.clientHeight <= 80; + }} + > + {props.items.map((item) => ( + + + + ))} + {props.children} +
+ ); +} diff --git a/typescript/web-ui-showcase/src/client/features/conversation/prompt-composer.tsx b/typescript/web-ui-showcase/src/client/features/conversation/prompt-composer.tsx new file mode 100644 index 0000000..32e649d --- /dev/null +++ b/typescript/web-ui-showcase/src/client/features/conversation/prompt-composer.tsx @@ -0,0 +1,590 @@ +import { + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, + type FormEvent, + type KeyboardEvent as ReactKeyboardEvent, +} from "react"; +import type { + SessionRuntimeView, + QueuedInputView, + SessionView, +} from "../../../shared/model.js"; +import type { SelectablePermissionMode } from "../../../shared/commands.js"; +import type { WorkspaceFileSearchResult } from "../../../shared/workspace-files.js"; +import { copy, queuedStateLabel } from "../../i18n/zh-cn.js"; +import { + ComposerSuggestionList, + suggestionOptionId, +} from "./composer-suggestion-list.js"; +import { + applySuggestion, + filterCommandSuggestions, + normalizePromptSuggestions, + parseSuggestionQuery, + resolveCompletedCommand, + type ActiveSuggestionQuery, + type ComposerSuggestion, +} from "./composer-suggestions.js"; +import { + ComposerControlMenu, + runtimeControlReason, + type RuntimeControlFailure, + type RuntimeControl, +} from "./composer-control-menu.js"; +import { ComposerDrafts } from "./composer-drafts.js"; +import { readContextSummary } from "./context-summary.js"; + +type Accepted = { commandId: string }; +const fileSuggestionDebounceMs = 200; +type FileSuggestionState = + | { status: "idle"; items: []; truncated: false } + | { status: "loading"; items: []; truncated: false } + | { status: "failed"; items: []; truncated: false } + | { + status: "ready"; + items: WorkspaceFileSearchResult["items"]; + truncated: boolean; + }; + +function suggestionQueryKey(query: ActiveSuggestionQuery | null): string | null { + return query === null + ? null + : `${query.kind}:${query.start}:${query.end}:${query.query}`; +} + +export type ComposerTarget = + | { + kind: "home"; + workspaceId: string | null; + start(text: string): Promise; + } + | { + kind: "session"; + session: SessionView; + runtime?: SessionRuntimeView; + send(text: string): Promise; + stop(): Promise; + setModel(model?: string): Promise; + setPermissionMode( + mode: SelectablePermissionMode, + ): Promise; + openMcp(): void; + refreshContext(): Promise; + modelFailure?: RuntimeControlFailure; + permissionFailure?: RuntimeControlFailure; + }; + +type CommonComposerProps = { + queued?: QueuedInputView[]; + cancel?: (uuid: string) => Promise; + searchWorkspaceFiles?: ( + sessionId: string, + query: string, + signal?: AbortSignal, + ) => Promise; +}; + +type PromptComposerProps = CommonComposerProps & { + target: ComposerTarget; + drafts: ComposerDrafts; + autoResuming?: boolean; + disabledReason?: string; +}; + +export function PromptComposer(props: PromptComposerProps): JSX.Element { + const { drafts, target } = props; + const session = target.kind === "session" ? target.session : undefined; + const runtime = target.kind === "session" ? target.runtime : undefined; + const draftKey = target.kind === "home" ? "home" : target.session.id; + const draft = drafts.read(draftKey); + const [, setDraftRevision] = useState(0); + const [cursor, setCursor] = useState(0); + const [error, setError] = useState(null); + const [activeIndex, setActiveIndex] = useState(0); + const [dismissedQueryKey, setDismissedQueryKey] = useState(null); + const [fileState, setFileState] = useState({ + status: "idle", + items: [], + truncated: false, + }); + const textarea = useRef(null); + const modelControl = useRef(null); + const permissionControl = useRef(null); + const restoreCaret = useRef(false); + const requestSequence = useRef(0); + const activeDraftKey = useRef(draftKey); + const submittingDrafts = useRef(new Set()); + const [, setSubmissionRevision] = useState(0); + activeDraftKey.current = draftKey; + const enabled = + !props.autoResuming && + props.disabledReason === undefined && + (session === undefined || + session.phase === "idle" || + session.phase === "running"); + const submitting = submittingDrafts.current.has(draftKey); + const activeQuery = parseSuggestionQuery(draft, cursor); + const queryKey = suggestionQueryKey(activeQuery); + const composerCommands = runtime?.composerCommands ?? []; + const commandSuggestions = useMemo( + () => + activeQuery?.kind === "command" + ? filterCommandSuggestions( + composerCommands, + activeQuery.query, + ) + : [], + [activeQuery?.kind, activeQuery?.query, composerCommands], + ); + const fileSuggestions = useMemo( + () => + activeQuery?.kind === "file" && fileState.status === "ready" + ? fileState.items.map((item) => ({ + kind: "file" as const, + id: `file:${item.source}:${item.mention}`, + ...item, + })) + : [], + [activeQuery?.kind, fileState], + ); + const suggestions = + activeQuery?.kind === "file" + ? fileSuggestions + : commandSuggestions; + const popupOpen = + activeQuery !== null && dismissedQueryKey !== queryKey; + const listboxId = `composer-suggestions-${draftKey}`; + const visibleActiveIndex = activeIndex < suggestions.length ? activeIndex : 0; + const activeSuggestion = suggestions[visibleActiveIndex]; + const suggestionIdentity = suggestions + .map((suggestion) => suggestion.id) + .join("\u0000"); + const contextSummary = readContextSummary( + runtime?.contextStatus, + runtime?.context, + ); + const promptSuggestions = normalizePromptSuggestions( + runtime?.promptSuggestions ?? [], + ); + + useEffect(() => setActiveIndex(0), [queryKey, suggestionIdentity]); + + useEffect(() => { + requestSequence.current += 1; + setError(null); + setCursor(drafts.read(draftKey).length); + setFileState({ status: "idle", items: [], truncated: false }); + setDismissedQueryKey(null); + }, [draftKey, drafts]); + + useEffect(() => { + if (activeQuery?.kind !== "file") { + requestSequence.current += 1; + setFileState({ status: "idle", items: [], truncated: false }); + return; + } + const sessionId = target.kind === "session" ? target.session.id : null; + if (props.searchWorkspaceFiles === undefined || sessionId === null) { + setFileState({ status: "failed", items: [], truncated: false }); + return; + } + const sequence = ++requestSequence.current; + const controller = new AbortController(); + let active = true; + setFileState({ status: "loading", items: [], truncated: false }); + const timeout = window.setTimeout(() => { + void props + .searchWorkspaceFiles?.(sessionId, activeQuery.query, controller.signal) + .then( + (result) => { + if (!active || requestSequence.current !== sequence) return; + setFileState({ status: "ready", ...result }); + }, + () => { + if ( + !active || + controller.signal.aborted || + requestSequence.current !== sequence + ) return; + setFileState({ status: "failed", items: [], truncated: false }); + }, + ); + }, fileSuggestionDebounceMs); + return () => { + active = false; + window.clearTimeout(timeout); + controller.abort(); + }; + }, [ + activeQuery?.kind, + activeQuery?.query, + props.searchWorkspaceFiles, + draftKey, + target.kind, + target.kind === "home" ? null : target.session.id, + ]); + + useLayoutEffect(() => { + if (!restoreCaret.current) return; + textarea.current?.focus(); + textarea.current?.setSelectionRange(cursor, cursor); + const frame = window.requestAnimationFrame(() => { + restoreCaret.current = false; + }); + return () => window.cancelAnimationFrame(frame); + }, [cursor, draft]); + + function writeDraft(value: string): void { + drafts.write(draftKey, value); + setDraftRevision((revision) => revision + 1); + } + + function clearDraft(key = draftKey): void { + drafts.clear(key); + if (key !== activeDraftKey.current) return; + setDraftRevision((revision) => revision + 1); + setCursor(0); + setDismissedQueryKey(null); + } + + function setDraftError( + key: string, + message: string | null, + ): void { + if (key === activeDraftKey.current) setError(message); + } + + function checkRuntimeControl(control: RuntimeControl): boolean { + if (target.kind !== "session") return false; + const reason = runtimeControlReason(runtime, control); + if (reason !== undefined) { + setError(reason); + return false; + } + setError(null); + return true; + } + + function focusRuntimeControl( + control: "model" | "permission", + ): boolean { + if (!checkRuntimeControl(control)) return false; + (control === "model" ? modelControl : permissionControl).current?.focus(); + return true; + } + + function openMcp(): boolean { + if (target.kind !== "session" || !checkRuntimeControl("mcp")) return false; + target.openMcp(); + return true; + } + + async function submitDraft(): Promise { + const text = draft.trim(); + if ( + !enabled || + text.length === 0 || + submittingDrafts.current.has(draftKey) + ) return; + const submittedKey = draftKey; + submittingDrafts.current.add(submittedKey); + setSubmissionRevision((revision) => revision + 1); + try { + if (target.kind === "home") { + await target.start(text); + setDraftError(submittedKey, null); + clearDraft(submittedKey); + return; + } + const completed = resolveCompletedCommand(text, composerCommands); + if (completed !== undefined) { + switch (completed.command.execution) { + case "sdk-input": + await target.send(text); + setDraftError(submittedKey, null); + clearDraft(submittedKey); + return; + case "model-control": + if (focusRuntimeControl("model")) clearDraft(submittedKey); + return; + case "permission-control": + if (focusRuntimeControl("permission")) clearDraft(submittedKey); + return; + case "mcp-control": + if (openMcp()) clearDraft(submittedKey); + return; + case "context-control": { + if (completed.argument.length > 0) { + setError("/context 不接受参数。"); + return; + } + await target.refreshContext(); + setDraftError(submittedKey, null); + clearDraft(submittedKey); + return; + } + } + } + await target.send(text); + setDraftError(submittedKey, null); + clearDraft(submittedKey); + } catch { + setDraftError(submittedKey, "命令请求未能提交,请重试。"); + } finally { + submittingDrafts.current.delete(submittedKey); + setSubmissionRevision((revision) => revision + 1); + } + } + + function submit(event: FormEvent): void { + event.preventDefault(); + void submitDraft(); + } + + function chooseSuggestion(suggestion: ComposerSuggestion): void { + if (activeQuery === null) return; + if (suggestion.kind === "command" && target.kind === "session") { + const execution = suggestion.command.execution; + if ( + execution === "model-control" || + execution === "permission-control" || + execution === "mcp-control" + ) { + const opened = + execution === "model-control" + ? focusRuntimeControl("model") + : execution === "permission-control" + ? focusRuntimeControl("permission") + : openMcp(); + if (opened) clearDraft(); + return; + } + } + const next = applySuggestion(draft, cursor, activeQuery, suggestion); + restoreCaret.current = true; + writeDraft(next.text); + setCursor(next.cursor); + if ( + suggestion.kind === "command" && + suggestion.command.execution !== "model-control" && + suggestion.command.execution !== "permission-control" && + suggestion.command.execution !== "mcp-control" + ) { + setDismissedQueryKey( + suggestionQueryKey(parseSuggestionQuery(next.text, next.cursor)), + ); + } else { + setDismissedQueryKey(null); + } + } + + function onKeyDown(event: ReactKeyboardEvent): void { + if (event.nativeEvent.isComposing) return; + if (event.key === "Enter" && event.shiftKey) return; + if ( + popupOpen && + activeSuggestion !== undefined && + (event.key === "Enter" || event.key === "Tab") + ) { + event.preventDefault(); + chooseSuggestion(activeSuggestion); + return; + } + if ( + popupOpen && + suggestions.length > 0 && + (event.key === "ArrowDown" || event.key === "ArrowUp") + ) { + event.preventDefault(); + const direction = event.key === "ArrowDown" ? 1 : -1; + setActiveIndex( + (index) => + (index + direction + suggestions.length) % suggestions.length, + ); + return; + } + if (popupOpen && event.key === "Escape") { + event.preventDefault(); + setDismissedQueryKey(queryKey); + return; + } + if (event.key === "Enter") { + event.preventDefault(); + void submitDraft(); + } + } + + return ( +
+ {(props.queued ?? []).length === 0 ? null : ( +
+ {(props.queued ?? []).map((item) => ( + + {item.uuid.slice(0, 8)}{" "} + {queuedStateLabel(item.state)} + + + ))} +
+ )} + {error === null ? null :

{error}

} + {draft.length > 0 || promptSuggestions.length === 0 ? null : ( +
+ {promptSuggestions.map((suggestion) => ( + + ))} +
+ )} + {popupOpen ? ( + + ) : null} +
+