diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..3011eeb
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,40 @@
+name: CI
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+
+jobs:
+ ci:
+ runs-on: ubuntu-latest
+ env:
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
+ TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 10
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+ cache: "pnpm"
+ cache-dependency-path: "**/pnpm-lock.yaml"
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Lint
+ run: pnpm lint
+
+ - name: Build
+ run: pnpm build
+
+ - name: test
+ run: pnpm test
diff --git a/.github/workflows/publish_app.yml b/.github/workflows/publish_app.yml
new file mode 100644
index 0000000..6648c14
--- /dev/null
+++ b/.github/workflows/publish_app.yml
@@ -0,0 +1,58 @@
+name: App Publisher
+
+on:
+ workflow_call:
+ inputs:
+ app:
+ required: true
+ type: string
+ image:
+ required: true
+ type: string
+ branch:
+ required: false
+ type: string
+ default: main
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Log in to GHCR
+ uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Docker metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ inputs.image }}
+ tags: |
+ ${{ github.event_name == 'release' && 'type=semver,pattern={{version}}' || github.event.inputs.tag }}
+
+ - name: Build & push image
+ id: push
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: docker/Dockerfile.web-app
+ build-args: |
+ APP_NAME=${{ inputs.app }}
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+
+ - name: Generate attestation
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: actions/attest-build-provenance@v1
+ with:
+ subject-name: ${{ inputs.image }}
+ subject-digest: ${{ steps.push.outputs.digest }}
+ push-to-registry: true
diff --git a/.github/workflows/publish_visr.yml b/.github/workflows/publish_visr.yml
new file mode 100644
index 0000000..f77b5c7
--- /dev/null
+++ b/.github/workflows/publish_visr.yml
@@ -0,0 +1,20 @@
+name: Publish ViSR Docker Image
+
+on:
+ release:
+ types: [published]
+
+ workflow_dispatch:
+ inputs:
+ branch:
+ description: "Branch to build from"
+ default: "main"
+ required: true
+
+jobs:
+ publish:
+ uses: ./.github/workflows/publish_app.yml
+ with:
+ app: visr
+ image: ghcr.io/DiamondLightSource/atlas/visr
+ branch: ${{ github.event.inputs.branch || 'main' }}
diff --git a/.gitignore b/.gitignore
index 51120e7..64718f1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
+
+# Turborepo
+.turbo
diff --git a/apps/visr/.dockerignore b/apps/visr/.dockerignore
new file mode 100644
index 0000000..3d938ea
--- /dev/null
+++ b/apps/visr/.dockerignore
@@ -0,0 +1,6 @@
+.dockerignore
+coverage
+node_modules
+dist
+README.md
+.git
\ No newline at end of file
diff --git a/apps/visr/.gitignore b/apps/visr/.gitignore
new file mode 100644
index 0000000..8b7e502
--- /dev/null
+++ b/apps/visr/.gitignore
@@ -0,0 +1,22 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/visr/.prettierignore b/apps/visr/.prettierignore
new file mode 100644
index 0000000..25983d8
--- /dev/null
+++ b/apps/visr/.prettierignore
@@ -0,0 +1,5 @@
+.github
+.yarn
+dist
+node_modules
+yarn.lock
diff --git a/apps/visr/.prettierrc b/apps/visr/.prettierrc
new file mode 100644
index 0000000..2aecfbd
--- /dev/null
+++ b/apps/visr/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "arrowParens": "avoid",
+ "semi": true,
+ "singleQuote": false,
+ "trailingComma": "all"
+}
\ No newline at end of file
diff --git a/apps/visr/.vscode/extensions.json b/apps/visr/.vscode/extensions.json
new file mode 100644
index 0000000..9edd4f1
--- /dev/null
+++ b/apps/visr/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+ "recommendations": ["esbenp.prettier-vscode"]
+}
\ No newline at end of file
diff --git a/apps/visr/.vscode/settings.json b/apps/visr/.vscode/settings.json
new file mode 100644
index 0000000..66dd16e
--- /dev/null
+++ b/apps/visr/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "prettier.requireConfig": true,
+ "prettier.configPath": "./.prettierrc",
+ "prettier.prettierPath": "./node_modules/prettier"
+}
diff --git a/apps/visr/LICENSE b/apps/visr/LICENSE
new file mode 100644
index 0000000..9c8f3ea
--- /dev/null
+++ b/apps/visr/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/apps/visr/README.md b/apps/visr/README.md
new file mode 100644
index 0000000..6421932
--- /dev/null
+++ b/apps/visr/README.md
@@ -0,0 +1 @@
+# ViSR UI
diff --git a/apps/visr/eslint.config.js b/apps/visr/eslint.config.js
new file mode 100644
index 0000000..d94e7de
--- /dev/null
+++ b/apps/visr/eslint.config.js
@@ -0,0 +1,23 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import tseslint from 'typescript-eslint'
+import { globalIgnores } from 'eslint/config'
+
+export default tseslint.config([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ tseslint.configs.recommended,
+ reactHooks.configs['recommended-latest'],
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ ecmaVersion: 2020,
+ globals: globals.browser,
+ },
+ },
+])
diff --git a/apps/visr/index.html b/apps/visr/index.html
new file mode 100644
index 0000000..8668e6d
--- /dev/null
+++ b/apps/visr/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ ViSR
+
+
+
+
+
+
diff --git a/apps/visr/package.json b/apps/visr/package.json
new file mode 100644
index 0000000..db8db1d
--- /dev/null
+++ b/apps/visr/package.json
@@ -0,0 +1,60 @@
+{
+ "name": "@atlas/visr",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "tsc -b && vite build",
+ "dev": "vite",
+ "lint": "eslint .",
+ "preview": "vite preview",
+ "relay": "relay-compiler",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "dependencies": {
+ "@diamondlightsource/davidia": "^1.0.3",
+ "@diamondlightsource/sci-react-ui": "^0.2.0",
+ "@emotion/react": "^11.14.0",
+ "@emotion/styled": "^11.14.1",
+ "@jsonforms/core": "3.6.0",
+ "@jsonforms/material-renderers": "3.6.0",
+ "@jsonforms/react": "3.6.0",
+ "@mui/icons-material": "^6.5.0",
+ "@mui/lab": "6.0.0-beta.22",
+ "@mui/material": "<7.0.0",
+ "@mui/x-date-pickers": "^7.17.0",
+ "ndarray": "^1.0.19",
+ "react-error-boundary": "^6.0.0",
+ "react-router-dom": "^7.7.1"
+ },
+ "devDependencies": {
+ "@atlas/vitest-conf": "workspace:*",
+ "@eslint/js": "^9.33.0",
+ "@mui/material": "^6.5.0",
+ "@mui/x-date-pickers": "^7.29.4",
+ "@types/ndarray": "^1.0.14",
+ "@types/react-relay": "^18.2.1",
+ "@types/relay-runtime": "^19.0.2",
+ "@vitejs/plugin-react-swc": "^3.11.0",
+ "ajv": "^8.17.1",
+ "babel-plugin-relay": "^20.1.1",
+ "eslint-plugin-react-hooks": "^5.2.0",
+ "eslint-plugin-react-refresh": "^0.4.20",
+ "globals": "^16.3.0",
+ "msw": "^2.10.4",
+ "react-relay": "^20.1.1",
+ "react-router-dom": "^7.8.0",
+ "relay-compiler": "^20.1.1",
+ "relay-runtime": "^20.1.1",
+ "vite": "^7.1.2",
+ "vite-plugin-relay": "^2.1.0",
+ "vitest": "*"
+ },
+ "packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417",
+ "msw": {
+ "workerDirectory": [
+ "public"
+ ]
+ }
+}
diff --git a/apps/visr/public/diamond.svg b/apps/visr/public/diamond.svg
new file mode 100644
index 0000000..0af1a17
--- /dev/null
+++ b/apps/visr/public/diamond.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/visr/public/mockServiceWorker.js b/apps/visr/public/mockServiceWorker.js
new file mode 100644
index 0000000..f22ebf3
--- /dev/null
+++ b/apps/visr/public/mockServiceWorker.js
@@ -0,0 +1,343 @@
+/* tslint:disable */
+
+/**
+ * Mock Service Worker.
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ */
+
+const PACKAGE_VERSION = "2.10.4";
+const INTEGRITY_CHECKSUM = "f5825c521429caf22a4dd13b66e243af";
+const IS_MOCKED_RESPONSE = Symbol("isMockedResponse");
+const activeClientIds = new Set();
+
+addEventListener("install", function () {
+ self.skipWaiting();
+});
+
+addEventListener("activate", function (event) {
+ event.waitUntil(self.clients.claim());
+});
+
+addEventListener("message", async function (event) {
+ const clientId = Reflect.get(event.source || {}, "id");
+
+ if (!clientId || !self.clients) {
+ return;
+ }
+
+ const client = await self.clients.get(clientId);
+
+ if (!client) {
+ return;
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: "window",
+ });
+
+ switch (event.data) {
+ case "KEEPALIVE_REQUEST": {
+ sendToClient(client, {
+ type: "KEEPALIVE_RESPONSE",
+ });
+ break;
+ }
+
+ case "INTEGRITY_CHECK_REQUEST": {
+ sendToClient(client, {
+ type: "INTEGRITY_CHECK_RESPONSE",
+ payload: {
+ packageVersion: PACKAGE_VERSION,
+ checksum: INTEGRITY_CHECKSUM,
+ },
+ });
+ break;
+ }
+
+ case "MOCK_ACTIVATE": {
+ activeClientIds.add(clientId);
+
+ sendToClient(client, {
+ type: "MOCKING_ENABLED",
+ payload: {
+ client: {
+ id: client.id,
+ frameType: client.frameType,
+ },
+ },
+ });
+ break;
+ }
+
+ case "MOCK_DEACTIVATE": {
+ activeClientIds.delete(clientId);
+ break;
+ }
+
+ case "CLIENT_CLOSED": {
+ activeClientIds.delete(clientId);
+
+ const remainingClients = allClients.filter(client => {
+ return client.id !== clientId;
+ });
+
+ // Unregister itself when there are no more clients
+ if (remainingClients.length === 0) {
+ self.registration.unregister();
+ }
+
+ break;
+ }
+ }
+});
+
+addEventListener("fetch", function (event) {
+ // Bypass navigation requests.
+ if (event.request.mode === "navigate") {
+ return;
+ }
+
+ // Opening the DevTools triggers the "only-if-cached" request
+ // that cannot be handled by the worker. Bypass such requests.
+ if (
+ event.request.cache === "only-if-cached" &&
+ event.request.mode !== "same-origin"
+ ) {
+ return;
+ }
+
+ // Bypass all requests when there are no active clients.
+ // Prevents the self-unregistered worked from handling requests
+ // after it's been deleted (still remains active until the next reload).
+ if (activeClientIds.size === 0) {
+ return;
+ }
+
+ const requestId = crypto.randomUUID();
+ event.respondWith(handleRequest(event, requestId));
+});
+
+/**
+ * @param {FetchEvent} event
+ * @param {string} requestId
+ */
+async function handleRequest(event, requestId) {
+ const client = await resolveMainClient(event);
+ const requestCloneForEvents = event.request.clone();
+ const response = await getResponse(event, client, requestId);
+
+ // Send back the response clone for the "response:*" life-cycle events.
+ // Ensure MSW is active and ready to handle the message, otherwise
+ // this message will pend indefinitely.
+ if (client && activeClientIds.has(client.id)) {
+ const serializedRequest = await serializeRequest(requestCloneForEvents);
+
+ // Clone the response so both the client and the library could consume it.
+ const responseClone = response.clone();
+
+ sendToClient(
+ client,
+ {
+ type: "RESPONSE",
+ payload: {
+ isMockedResponse: IS_MOCKED_RESPONSE in response,
+ request: {
+ id: requestId,
+ ...serializedRequest,
+ },
+ response: {
+ type: responseClone.type,
+ status: responseClone.status,
+ statusText: responseClone.statusText,
+ headers: Object.fromEntries(responseClone.headers.entries()),
+ body: responseClone.body,
+ },
+ },
+ },
+ responseClone.body ? [serializedRequest.body, responseClone.body] : [],
+ );
+ }
+
+ return response;
+}
+
+/**
+ * Resolve the main client for the given event.
+ * Client that issues a request doesn't necessarily equal the client
+ * that registered the worker. It's with the latter the worker should
+ * communicate with during the response resolving phase.
+ * @param {FetchEvent} event
+ * @returns {Promise}
+ */
+async function resolveMainClient(event) {
+ const client = await self.clients.get(event.clientId);
+
+ if (activeClientIds.has(event.clientId)) {
+ return client;
+ }
+
+ if (client?.frameType === "top-level") {
+ return client;
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: "window",
+ });
+
+ return allClients
+ .filter(client => {
+ // Get only those clients that are currently visible.
+ return client.visibilityState === "visible";
+ })
+ .find(client => {
+ // Find the client ID that's recorded in the
+ // set of clients that have registered the worker.
+ return activeClientIds.has(client.id);
+ });
+}
+
+/**
+ * @param {FetchEvent} event
+ * @param {Client | undefined} client
+ * @param {string} requestId
+ * @returns {Promise}
+ */
+async function getResponse(event, client, requestId) {
+ // Clone the request because it might've been already used
+ // (i.e. its body has been read and sent to the client).
+ const requestClone = event.request.clone();
+
+ function passthrough() {
+ // Cast the request headers to a new Headers instance
+ // so the headers can be manipulated with.
+ const headers = new Headers(requestClone.headers);
+
+ // Remove the "accept" header value that marked this request as passthrough.
+ // This prevents request alteration and also keeps it compliant with the
+ // user-defined CORS policies.
+ const acceptHeader = headers.get("accept");
+ if (acceptHeader) {
+ const values = acceptHeader.split(",").map(value => value.trim());
+ const filteredValues = values.filter(
+ value => value !== "msw/passthrough",
+ );
+
+ if (filteredValues.length > 0) {
+ headers.set("accept", filteredValues.join(", "));
+ } else {
+ headers.delete("accept");
+ }
+ }
+
+ return fetch(requestClone, { headers });
+ }
+
+ // Bypass mocking when the client is not active.
+ if (!client) {
+ return passthrough();
+ }
+
+ // Bypass initial page load requests (i.e. static assets).
+ // The absence of the immediate/parent client in the map of the active clients
+ // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+ // and is not ready to handle requests.
+ if (!activeClientIds.has(client.id)) {
+ return passthrough();
+ }
+
+ // Notify the client that a request has been intercepted.
+ const serializedRequest = await serializeRequest(event.request);
+ const clientMessage = await sendToClient(
+ client,
+ {
+ type: "REQUEST",
+ payload: {
+ id: requestId,
+ ...serializedRequest,
+ },
+ },
+ [serializedRequest.body],
+ );
+
+ switch (clientMessage.type) {
+ case "MOCK_RESPONSE": {
+ return respondWithMock(clientMessage.data);
+ }
+
+ case "PASSTHROUGH": {
+ return passthrough();
+ }
+ }
+
+ return passthrough();
+}
+
+/**
+ * @param {Client} client
+ * @param {any} message
+ * @param {Array} transferrables
+ * @returns {Promise}
+ */
+function sendToClient(client, message, transferrables = []) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+
+ channel.port1.onmessage = event => {
+ if (event.data && event.data.error) {
+ return reject(event.data.error);
+ }
+
+ resolve(event.data);
+ };
+
+ client.postMessage(message, [
+ channel.port2,
+ ...transferrables.filter(Boolean),
+ ]);
+ });
+}
+
+/**
+ * @param {Response} response
+ * @returns {Response}
+ */
+function respondWithMock(response) {
+ // Setting response status code to 0 is a no-op.
+ // However, when responding with a "Response.error()", the produced Response
+ // instance will have status code set to 0. Since it's not possible to create
+ // a Response instance with status code 0, handle that use-case separately.
+ if (response.status === 0) {
+ return Response.error();
+ }
+
+ const mockedResponse = new Response(response.body, response);
+
+ Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
+ value: true,
+ enumerable: true,
+ });
+
+ return mockedResponse;
+}
+
+/**
+ * @param {Request} request
+ */
+async function serializeRequest(request) {
+ return {
+ url: request.url,
+ mode: request.mode,
+ method: request.method,
+ headers: Object.fromEntries(request.headers.entries()),
+ cache: request.cache,
+ credentials: request.credentials,
+ destination: request.destination,
+ integrity: request.integrity,
+ redirect: request.redirect,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ body: await request.arrayBuffer(),
+ keepalive: request.keepalive,
+ };
+}
diff --git a/apps/visr/relay.config.json b/apps/visr/relay.config.json
new file mode 100644
index 0000000..cb1be38
--- /dev/null
+++ b/apps/visr/relay.config.json
@@ -0,0 +1,7 @@
+{
+ "src": "./src",
+ "language": "typescript",
+ "schema": "./supergraph.graphql",
+ "exclude": ["**/node_modules/**", "**/__mocks__/**", "**/__generated__/**"],
+ "eagerEsModules": true
+}
diff --git a/apps/visr/src/RelayEnvironment.ts b/apps/visr/src/RelayEnvironment.ts
new file mode 100644
index 0000000..3e85e88
--- /dev/null
+++ b/apps/visr/src/RelayEnvironment.ts
@@ -0,0 +1,34 @@
+import {
+ Environment,
+ Network,
+ RecordSource,
+ Store,
+ type FetchFunction,
+} from "relay-runtime";
+
+const HTTP_ENDPOINT = "/api/graphql";
+const fetchFn: FetchFunction = async (request, variables) => {
+ const resp = await fetch(HTTP_ENDPOINT, {
+ method: "POST",
+ headers: {
+ Accept:
+ "application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ query: request.text,
+ variables,
+ }),
+ });
+
+ return await resp.json();
+};
+
+function createRelayEnvironment() {
+ return new Environment({
+ network: Network.create(fetchFn),
+ store: new Store(new RecordSource()),
+ });
+}
+
+export const RelayEnvironment = createRelayEnvironment();
diff --git a/apps/visr/src/components/InstrumentSessionSelection/InstrumentSession.tsx b/apps/visr/src/components/InstrumentSessionSelection/InstrumentSession.tsx
new file mode 100644
index 0000000..d0f854f
--- /dev/null
+++ b/apps/visr/src/components/InstrumentSessionSelection/InstrumentSession.tsx
@@ -0,0 +1,46 @@
+import { visitToText, type Visit } from "@diamondlightsource/sci-react-ui";
+import { useLazyLoadQuery } from "react-relay/hooks";
+import { graphql } from "relay-runtime";
+import type { InstrumentSessionQuery as InstrumentSessionQueryType } from "./__generated__/InstrumentSessionQuery.graphql";
+
+const instrumentSessionQuery = graphql`
+ query InstrumentSessionQuery($instrumentName: String!) {
+ instrument(instrumentName: $instrumentName) {
+ instrumentSessions {
+ instrumentSessionNumber
+ proposal {
+ proposalCategory
+ proposalNumber
+ }
+ }
+ }
+ }
+`;
+
+function GetInstrumentSessions() {
+ const data = useLazyLoadQuery(
+ instrumentSessionQuery,
+ { instrumentName: "ViSR" },
+ );
+
+ const sessionListLen = data.instrument?.instrumentSessions.length ?? 1;
+ const sessionsList = [];
+
+ for (let i = 0; i < sessionListLen; i++) {
+ const visit: Visit = {
+ proposalCode:
+ data.instrument?.instrumentSessions[
+ i
+ ].proposal?.proposalCategory?.toLowerCase() ?? "cm",
+ proposalNumber:
+ data.instrument?.instrumentSessions[i].proposal?.proposalNumber ??
+ 12345,
+ number:
+ data.instrument?.instrumentSessions[i].instrumentSessionNumber ?? 1,
+ };
+ sessionsList.push(visitToText(visit));
+ }
+ return sessionsList;
+}
+
+export default GetInstrumentSessions;
diff --git a/apps/visr/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx b/apps/visr/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx
new file mode 100644
index 0000000..c10225d
--- /dev/null
+++ b/apps/visr/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx
@@ -0,0 +1,93 @@
+import { useState } from "react";
+import { visitToText, VisitInput } from "@diamondlightsource/sci-react-ui";
+import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
+import {
+ Divider,
+ List,
+ ListItemButton,
+ ListItemText,
+ Menu,
+ MenuItem,
+} from "@mui/material";
+
+function InstrumentSessionView({ sessionsList }: { sessionsList: string[] }) {
+ const { instrumentSession, setInstrumentSession } = useInstrumentSession();
+
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [selectedIndex, setSelectedIndex] = useState(1);
+ const open = Boolean(anchorEl);
+ const handleClickListItem = (event: React.MouseEvent) => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleMenuItemClick = (
+ event: React.MouseEvent,
+ index: number,
+ ) => {
+ setSelectedIndex(index);
+ setAnchorEl(null);
+ setInstrumentSession(event.currentTarget.textContent ?? "");
+ };
+
+ const handleClose = () => {
+ setAnchorEl(null);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+export default InstrumentSessionView;
diff --git a/apps/visr/src/components/InstrumentSessionSelection/__generated__/InstrumentSessionQuery.graphql.ts b/apps/visr/src/components/InstrumentSessionSelection/__generated__/InstrumentSessionQuery.graphql.ts
new file mode 100644
index 0000000..89ed37f
--- /dev/null
+++ b/apps/visr/src/components/InstrumentSessionSelection/__generated__/InstrumentSessionQuery.graphql.ts
@@ -0,0 +1,131 @@
+/**
+ * @generated SignedSource<>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+export type InstrumentSessionQuery$variables = {
+ instrumentName: string;
+};
+export type InstrumentSessionQuery$data = {
+ readonly instrument: {
+ readonly instrumentSessions: ReadonlyArray<{
+ readonly instrumentSessionNumber: number;
+ readonly proposal: {
+ readonly proposalCategory: string | null | undefined;
+ readonly proposalNumber: number;
+ } | null | undefined;
+ }>;
+ } | null | undefined;
+};
+export type InstrumentSessionQuery = {
+ response: InstrumentSessionQuery$data;
+ variables: InstrumentSessionQuery$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "instrumentName"
+ }
+],
+v1 = [
+ {
+ "alias": null,
+ "args": [
+ {
+ "kind": "Variable",
+ "name": "instrumentName",
+ "variableName": "instrumentName"
+ }
+ ],
+ "concreteType": "Instrument",
+ "kind": "LinkedField",
+ "name": "instrument",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "InstrumentSession",
+ "kind": "LinkedField",
+ "name": "instrumentSessions",
+ "plural": true,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "instrumentSessionNumber",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "Proposal",
+ "kind": "LinkedField",
+ "name": "proposal",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "proposalCategory",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "proposalNumber",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "InstrumentSessionQuery",
+ "selections": (v1/*: any*/),
+ "type": "Query",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "InstrumentSessionQuery",
+ "selections": (v1/*: any*/)
+ },
+ "params": {
+ "cacheID": "625bde94466c698bcacfb6d41e195cbe",
+ "id": null,
+ "metadata": {},
+ "name": "InstrumentSessionQuery",
+ "operationKind": "query",
+ "text": "query InstrumentSessionQuery(\n $instrumentName: String!\n) {\n instrument(instrumentName: $instrumentName) {\n instrumentSessions {\n instrumentSessionNumber\n proposal {\n proposalCategory\n proposalNumber\n }\n }\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "3cb865e4a1f45b6db5b70c2cb201f4e7";
+
+export default node;
diff --git a/apps/visr/src/components/NumberTextField.tsx b/apps/visr/src/components/NumberTextField.tsx
new file mode 100644
index 0000000..ddaf0be
--- /dev/null
+++ b/apps/visr/src/components/NumberTextField.tsx
@@ -0,0 +1,48 @@
+import { TextField } from "@mui/material";
+import type { SpectroscopyFormData } from "./SpectroscopyForm";
+
+type NumberTextFieldProps = {
+ formData: SpectroscopyFormData;
+ setFormData: (f: SpectroscopyFormData) => void;
+ field: keyof SpectroscopyFormData;
+ step: number;
+ label: string;
+};
+
+const NumberTextField = ({
+ formData,
+ setFormData,
+ field,
+ step = 1,
+ label = field,
+}: NumberTextFieldProps) => {
+ const handleChange = (
+ e: React.ChangeEvent,
+ ) => {
+ const value = e.target.value;
+ const parsedValue =
+ value === ""
+ ? 0
+ : Number.isInteger(step) // parse to Int or Float depending on step
+ ? parseInt(value, 10)
+ : parseFloat(value);
+
+ setFormData({
+ ...formData,
+ [field]: parsedValue,
+ });
+ };
+
+ return (
+
+ );
+};
+
+export default NumberTextField;
diff --git a/apps/visr/src/components/PlanBrowser/PlanBrowser.test.tsx b/apps/visr/src/components/PlanBrowser/PlanBrowser.test.tsx
new file mode 100644
index 0000000..7ec51a4
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/PlanBrowser.test.tsx
@@ -0,0 +1,83 @@
+import type { Plan } from "../../utils/api";
+import { render, screen, userEvent } from "@atlas/vitest-conf";
+import PlanBrowser from "./PlanBrowser";
+
+const plans: Plan[] = [
+ { name: "Plan 1", schema: {}, description: "" },
+ { name: "Plan 2", schema: {}, description: "" },
+ { name: "Plan 3", schema: {}, description: "" },
+];
+
+const renderPlan = (plan: Plan) => (
+ {plan.name}
+);
+
+function renderBrowser() {
+ return render( );
+}
+
+describe("PlanBrowser", () => {
+ it("shows a placeholder before initial plan selection", () => {
+ renderBrowser();
+
+ expect(screen.getByText("Select a plan")).toBeInTheDocument();
+ expect(
+ screen.getByText("Choose from the list on the left to see details."),
+ ).toBeInTheDocument();
+ });
+
+ it("does not invoke renderPlan before selection", () => {
+ const mockRender = vi.fn();
+ render( );
+ expect(mockRender).not.toBeCalled();
+ });
+
+ it("renders plan details when a plan is selected", async () => {
+ renderBrowser();
+
+ const selectedPlan = screen.getByRole("button", { name: "Plan 2" });
+ const user = userEvent.setup();
+ await user.click(selectedPlan);
+
+ // placeholder disappears...
+ expect(screen.queryByText("Select a plan")).not.toBeInTheDocument();
+
+ // ...plan details appear
+ const planDetails = screen.getByTestId("plan-view");
+ expect(planDetails).toBeInTheDocument();
+ expect(planDetails).toHaveTextContent("Plan 2");
+ });
+
+ it("renders plan details with every selection", async () => {
+ renderBrowser();
+
+ const user = userEvent.setup();
+ await user.click(screen.getByRole("button", { name: "Plan 3" }));
+
+ const planDetails = screen.getByTestId("plan-view");
+ expect(planDetails).toBeInTheDocument();
+ expect(planDetails).toHaveTextContent("Plan 3");
+
+ await user.click(screen.getByRole("button", { name: "Plan 1" }));
+ expect(planDetails).toHaveTextContent("Plan 1");
+ });
+
+ it("persists plan details through search/filtering", async () => {
+ renderBrowser();
+ const user = userEvent.setup();
+
+ // select plan 1
+ await user.click(screen.getByRole("button", { name: "Plan 1" }));
+
+ // plan 1 details appear
+ const planDetails = screen.getByTestId("plan-view");
+ expect(planDetails).toHaveTextContent("Plan 1");
+
+ // search for a different plan
+ const searchbox = screen.getByRole("textbox", { name: /search plans/i });
+ await user.type(searchbox, "Plan 3");
+
+ // but user has not selected it, so plan 1 details remain
+ expect(planDetails).toHaveTextContent("Plan 1");
+ });
+});
diff --git a/apps/visr/src/components/PlanBrowser/PlanBrowser.tsx b/apps/visr/src/components/PlanBrowser/PlanBrowser.tsx
new file mode 100644
index 0000000..d5a1174
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/PlanBrowser.tsx
@@ -0,0 +1,60 @@
+import { useState, type ReactNode } from "react";
+import type { Plan } from "../../utils/api";
+import {
+ Box,
+ Container,
+ Grid2 as Grid,
+ Paper,
+ Typography,
+} from "@mui/material";
+import SearchablePlanList from "./SearchablePlanList";
+
+export type PlanBrowserProps = {
+ plans: Plan[];
+ renderPlan: (plan: Plan) => ReactNode;
+};
+
+export default function PlanBrowser({ plans, renderPlan }: PlanBrowserProps) {
+ const [selectedPlan, setSelectedPlan] = useState(null);
+
+ return (
+
+
+
+
+
+
+
+
+
+ {selectedPlan ? (
+
+ {renderPlan(selectedPlan)}
+
+ ) : (
+
+
+ Select a plan
+
+
+ Choose from the list on the left to see details.
+
+
+ )}
+
+
+
+
+ );
+}
diff --git a/apps/visr/src/components/PlanBrowser/PlanParameters.test.tsx b/apps/visr/src/components/PlanBrowser/PlanParameters.test.tsx
new file mode 100644
index 0000000..cb7f3e7
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/PlanParameters.test.tsx
@@ -0,0 +1,54 @@
+import type { Plan } from "../../utils/api";
+import { render, screen } from "@atlas/vitest-conf";
+
+import PlanParameters from "./PlanParameters";
+import { InstrumentSessionProvider } from "../../context/instrumentSession/InstrumentSessionProvider";
+
+const mockJsonFormsImpl = vi.fn(() => {
+ return
;
+});
+
+vi.mock("@jsonforms/react", () => {
+ return {
+ JsonForms: () => mockJsonFormsImpl(),
+ };
+});
+
+const plan: Plan = {
+ name: "hi_plan",
+ description: "Says hi to you",
+ schema: {
+ properties: { name: { title: "Name", type: "string" } },
+ },
+};
+
+describe("PlanParameters", () => {
+ it("renders a plan's name, description, parameters, session, and run button", () => {
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText(plan.name)).toBeInTheDocument();
+ expect(screen.getByText(plan.description!)).toBeInTheDocument();
+ expect(screen.getByTestId("jsonforms-sentinel")).toBeInTheDocument();
+ expect(screen.getByRole("textbox", { name: "Instrument Session" }));
+ expect(screen.getByRole("button", { name: "Run" }));
+ });
+
+ it("renders fallback UI if JSON Forms component fails", async () => {
+ // this time JsonForms will throw
+ mockJsonFormsImpl.mockImplementation(() => {
+ throw new Error("I can't do it!");
+ });
+
+ render(
+
+
+ ,
+ );
+
+ expect(screen.getByText("UI unavailable")).toBeInTheDocument();
+ });
+});
diff --git a/apps/visr/src/components/PlanBrowser/PlanParameters.tsx b/apps/visr/src/components/PlanBrowser/PlanParameters.tsx
new file mode 100644
index 0000000..db50577
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/PlanParameters.tsx
@@ -0,0 +1,76 @@
+import { useState } from "react";
+import { Box, TextField, Typography } from "@mui/material";
+import { JsonForms } from "@jsonforms/react";
+import {
+ materialRenderers,
+ materialCells,
+} from "@jsonforms/material-renderers";
+import sanitizeSchema from "../../utils/schema";
+import type { Plan } from "../../utils/api";
+import RunPlanButton from "../RunPlanButton";
+import { useInstrumentSession } from "../../context/instrumentSession/useInstrumentSession";
+
+import { ErrorBoundary } from "react-error-boundary";
+
+/**
+ * If the UI generation fails, we show a simple apology
+ */
+function UIFallback() {
+ return (
+
+ UI unavailable
+
+ );
+}
+type PlanParametersProps = {
+ plan: Plan;
+};
+
+const PlanParameters: React.FC = (
+ props: PlanParametersProps,
+) => {
+ const schema = sanitizeSchema(props.plan.schema);
+
+ // const renderers = materialRenderers;
+ const [planParameters, setPlanParameters] = useState({});
+ const { instrumentSession, setInstrumentSession } = useInstrumentSession();
+
+ return (
+
+
+ {props.plan.name}
+
+ {props.plan.description && (
+
+ {props.plan.description}
+
+ )}
+ setPlanParameters(data)}
+ />
+ setInstrumentSession(e.target.value)}
+ >
+
+
+
+
+ );
+};
+
+export default PlanParameters;
diff --git a/apps/visr/src/components/PlanBrowser/SearchablePlanList.test.tsx b/apps/visr/src/components/PlanBrowser/SearchablePlanList.test.tsx
new file mode 100644
index 0000000..2ac9fd2
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/SearchablePlanList.test.tsx
@@ -0,0 +1,161 @@
+import * as React from "react";
+import { render, screen, within, userEvent } from "@atlas/vitest-conf";
+import SearchablePlanList from "./SearchablePlanList";
+import type { Plan } from "../../utils/api";
+
+const plans: Plan[] = [
+ { name: "Align Beam", description: "", schema: {} },
+ { name: "Dark Current", description: "", schema: {} },
+ { name: "Flat Field", description: "", schema: {} },
+];
+
+function renderList(
+ overrides?: Partial>,
+) {
+ const updateSelection = vi.fn();
+ const props: React.ComponentProps = {
+ plans,
+ selectedPlan: null,
+ updateSelection,
+ ...overrides,
+ };
+ const utils = render( );
+
+ // merge useful things for the test
+ return { updateSelection, props, ...utils };
+}
+
+describe("SearchablePlanList", () => {
+ it("renders search, heading, and all plans initially", () => {
+ renderList();
+
+ // search field
+ expect(
+ screen.getByRole("textbox", { name: /search plans/i }),
+ ).toBeInTheDocument();
+
+ // heading
+ expect(screen.getByText("Plans")).toBeInTheDocument();
+
+ // all items
+ for (const p of plans) {
+ expect(screen.getByRole("button", { name: p.name })).toBeInTheDocument();
+ }
+
+ // no selection initially
+ for (const p of plans) {
+ expect(screen.getByRole("button", { name: p.name })).toHaveAttribute(
+ "aria-selected",
+ "false",
+ );
+ }
+ });
+
+ it("calls updateSelection with the clicked plan", async () => {
+ const user = userEvent.setup();
+ const { updateSelection } = renderList();
+
+ const item = screen.getByRole("button", { name: "Flat Field" });
+ await user.click(item);
+
+ expect(updateSelection).toHaveBeenCalledTimes(1);
+ expect(updateSelection).toHaveBeenCalledWith({
+ name: "Flat Field",
+ schema: {},
+ description: "",
+ });
+ });
+
+ it("reflects controlled selection via selectedPlan prop", async () => {
+ const { props, rerender } = renderList();
+
+ const selectedButton = screen.getByRole("button", { name: "Dark Current" });
+
+ // none selected initially
+ expect(selectedButton).toHaveAttribute("aria-selected", "false");
+
+ // re-render with selected plan
+ const selected = { name: "Dark Current", schema: {}, description: "" };
+ rerender( );
+ // selected plan marked so with aria-selected
+ expect(selectedButton).toHaveAttribute("aria-selected", "true");
+ // others remain unselected
+ expect(screen.getByRole("button", { name: "Align Beam" })).toHaveAttribute(
+ "aria-selected",
+ "false",
+ );
+ });
+
+ it("filters plans by case-insensitive search", async () => {
+ const user = userEvent.setup();
+ renderList();
+
+ const search = screen.getByRole("textbox", { name: /search plans/i });
+ await user.type(search, "dark"); // lower-case query
+
+ expect(
+ screen.getByRole("button", { name: "Dark Current" }),
+ ).toBeInTheDocument();
+ expect(
+ screen.queryByRole("button", { name: "Align Beam" }),
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole("button", { name: "Flat Field" }),
+ ).not.toBeInTheDocument();
+
+ // Case-insensitive check
+ await user.clear(search);
+ await user.type(search, "ALIG");
+ expect(
+ screen.getByRole("button", { name: "Align Beam" }),
+ ).toBeInTheDocument();
+ });
+
+ it("shows empty-state message when no plans match search", async () => {
+ const user = userEvent.setup();
+ renderList();
+
+ const search = screen.getByRole("textbox", { name: /search plans/i });
+ await user.type(search, "zzz");
+
+ expect(screen.getByText(/no plans match/i)).toBeInTheDocument();
+ });
+
+ it("handles empty plans array", () => {
+ renderList({ plans: [] });
+ // List is empty, but the heading and search are present
+ expect(screen.getByText("Plans")).toBeInTheDocument();
+ expect(
+ screen.getByRole("textbox", { name: /search plans/i }),
+ ).toBeInTheDocument();
+ });
+
+ it("shows all items after clearing a search", async () => {
+ renderList();
+ const user = userEvent.setup();
+
+ const planList = screen.getByRole("list");
+ const { getAllByRole, queryAllByRole } = within(planList);
+
+ // three initial plans
+ expect(getAllByRole("button")).toHaveLength(3);
+
+ // search
+ const search = screen.getByRole("textbox", { name: /search plans/i });
+ await user.type(search, "zzz");
+
+ // no matches
+ expect(screen.getByText(/no plans match/i)).toBeInTheDocument();
+ // no plans shown
+ expect(queryAllByRole("button")).toHaveLength(0);
+
+ // clear search
+ await user.clear(search);
+
+ // no matches label disappears
+ expect(screen.queryByText(/no plans match/i)).not.toBeInTheDocument();
+
+ // three plans again
+ expect(getAllByRole("button")).toHaveLength(3);
+ });
+});
diff --git a/apps/visr/src/components/PlanBrowser/SearchablePlanList.tsx b/apps/visr/src/components/PlanBrowser/SearchablePlanList.tsx
new file mode 100644
index 0000000..5a9873c
--- /dev/null
+++ b/apps/visr/src/components/PlanBrowser/SearchablePlanList.tsx
@@ -0,0 +1,71 @@
+import {
+ Box,
+ Divider,
+ List,
+ ListItemButton,
+ TextField,
+ Typography,
+} from "@mui/material";
+import type { Plan } from "../../utils/api";
+import { useMemo, useState } from "react";
+
+type Props = {
+ plans: Plan[];
+ selectedPlan: Plan | null;
+ updateSelection: (plan: Plan) => void;
+};
+export default function SearchablePlanList({
+ plans,
+ selectedPlan,
+ updateSelection,
+}: Props) {
+ const [query, setQuery] = useState("");
+
+ const matchingPlans = useMemo(() => {
+ const q = query.trim().toLowerCase();
+ if (!q) return plans;
+ return plans.filter(plan => plan.name.toLowerCase().includes(q));
+ }, [plans, query]);
+
+ return (
+ <>
+
+ setQuery(e.target.value)}
+ />
+
+
+
+
+ Plans
+
+
+
+ {matchingPlans.map(plan => {
+ const selected = selectedPlan?.name === plan.name;
+ return (
+ updateSelection(plan)}
+ >
+ {plan.name}
+
+ );
+ })}
+ {matchingPlans.length === 0 && plans.length > 0 && (
+
+
+ No plans match “{query}”.
+
+
+ )}
+
+ >
+ );
+}
diff --git a/apps/visr/src/components/RawSpectroscopyData.tsx b/apps/visr/src/components/RawSpectroscopyData.tsx
new file mode 100644
index 0000000..517fd3b
--- /dev/null
+++ b/apps/visr/src/components/RawSpectroscopyData.tsx
@@ -0,0 +1,113 @@
+import { ImagePlot, type NDT } from "@diamondlightsource/davidia";
+import Box from "@mui/material/Box";
+import ndarray from "ndarray";
+import { useSpectroscopyData, type RGBColour } from "./useSpectroscopyData";
+
+type RGBColor = "red" | "green" | "blue" | "gray";
+
+function toNDT(matrix: (number | null)[][], colour: RGBColor): NDT {
+ if (!matrix?.length || !matrix[0]?.length) {
+ return EMPTY_NDT; // skip invalid input
+ }
+ const height = matrix.length;
+ const width = matrix[0].length;
+
+ // Flatten and filter out nulls for normalisation
+ const flat = matrix.flat();
+ const valid = flat.filter((v): v is number => v !== null && !isNaN(v));
+
+ // Avoid crashes when no valid values
+ const min = valid.length ? Math.min(...valid) : 0;
+ const max = valid.length ? Math.max(...valid) : 1;
+ const scale = max > min ? 255 / (max - min) : 1;
+
+ const rgb = new Uint8Array(width * height * 3);
+
+ for (let i = 0; i < flat.length; i++) {
+ const v = flat[i];
+ let scaled = 0;
+ if (v !== null && !isNaN(v)) {
+ scaled = Math.round((v - min) * scale);
+ } // else stays 0 (black)
+
+ switch (colour) {
+ case "red":
+ rgb[i * 3] = scaled;
+ break;
+ case "green":
+ rgb[i * 3 + 1] = scaled;
+ break;
+ case "blue":
+ rgb[i * 3 + 2] = scaled;
+ break;
+ case "gray":
+ rgb[i * 3] = scaled;
+ rgb[i * 3 + 1] = scaled;
+ rgb[i * 3 + 2] = scaled;
+ break;
+ }
+ }
+
+ return ndarray(rgb, [height, width, 3]) as NDT;
+}
+/** Placeholder empty gray dataset */
+const EMPTY_NDT = toNDT([[0]], "gray");
+
+/** Return type of `/api/data/map` */
+interface MapResponse {
+ values: (number | null)[][];
+}
+
+async function fetchMap(filepath: string, datapath: string, colour: RGBColour) {
+ const url = `/api/data/map?filepath=${encodeURIComponent(filepath)}&datapath=${encodeURIComponent(datapath)}`;
+ const resp = await fetch(url);
+ if (!resp.ok) throw new Error(resp.statusText);
+ const mapResponse: MapResponse = await resp.json();
+ return toNDT(mapResponse.values, colour);
+}
+
+function RawSpectroscopyData() {
+ const { data } = useSpectroscopyData(fetchMap);
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default RawSpectroscopyData;
diff --git a/apps/visr/src/components/RunPlanButton.tsx b/apps/visr/src/components/RunPlanButton.tsx
new file mode 100644
index 0000000..f4f477d
--- /dev/null
+++ b/apps/visr/src/components/RunPlanButton.tsx
@@ -0,0 +1,34 @@
+import { Button } from "@mui/material";
+
+import { createAndStartTask, type TaskRequest } from "../utils/api";
+
+type RunPlanButtonProps = {
+ name: string;
+ params: object;
+ instrumentSession: string;
+};
+
+const RunPlanButton = ({
+ name,
+ params,
+ instrumentSession,
+}: RunPlanButtonProps) => {
+ return (
+ {
+ const taskRequest: TaskRequest = {
+ name: name,
+ params: params,
+ instrument_session: instrumentSession,
+ };
+ await createAndStartTask(taskRequest);
+ }}
+ >
+ Run
+
+ );
+};
+
+export default RunPlanButton;
diff --git a/apps/visr/src/components/SpectroscopyForm.tsx b/apps/visr/src/components/SpectroscopyForm.tsx
new file mode 100644
index 0000000..8bf2d40
--- /dev/null
+++ b/apps/visr/src/components/SpectroscopyForm.tsx
@@ -0,0 +1,102 @@
+import { useState } from "react";
+import { Box, TextField } from "@mui/material";
+import NumberTextField from "./NumberTextField";
+import RunPlanButton from "./RunPlanButton";
+import RawSpectroscopyData from "./RawSpectroscopyData";
+import { useInstrumentSession } from "../context/instrumentSession/useInstrumentSession";
+
+export type SpectroscopyFormData = {
+ total_number_of_scan_points: number;
+ grid_size: number;
+ grid_origin_x: number;
+ grid_origin_y: number;
+ exposure_time: number;
+};
+
+function SpectroscopyForm() {
+ const [formData, setFormData] = useState({
+ total_number_of_scan_points: 25,
+ grid_size: 5.0,
+ grid_origin_x: 0.0,
+ grid_origin_y: 0.0,
+ exposure_time: 0.1,
+ });
+
+ const { instrumentSession, setInstrumentSession } = useInstrumentSession();
+
+ return (
+
+
+
+
+
+
+
+
+ setInstrumentSession(e.target.value)}
+ />
+
+
+
+
+
+ );
+}
+
+export default SpectroscopyForm;
diff --git a/apps/visr/src/components/SubmissionForm.tsx b/apps/visr/src/components/SubmissionForm.tsx
new file mode 100644
index 0000000..5500cc0
--- /dev/null
+++ b/apps/visr/src/components/SubmissionForm.tsx
@@ -0,0 +1,30 @@
+import { useFragment } from "react-relay";
+import { type JSONObject, type Visit } from "../utils/types";
+import type { JsonSchema, UISchemaElement } from "@jsonforms/core";
+import type { workflowTemplateFragment$key } from "../graphql/__generated__/workflowTemplateFragment.graphql";
+import { workflowTemplateFragment } from "../graphql/workflowTemplateFragment";
+import TemplateSubmissionForm from "./TemplateSubmissionForm";
+
+const SubmissionForm = (props: {
+ template: workflowTemplateFragment$key;
+ prepopulatedParameters?: JSONObject;
+ visit?: Visit;
+ onSubmit: (visit: Visit, parameters: object) => void;
+}) => {
+ const data = useFragment(workflowTemplateFragment, props.template);
+ return (
+
+ );
+};
+
+export default SubmissionForm;
diff --git a/apps/visr/src/components/SubmittedMessagesList.tsx b/apps/visr/src/components/SubmittedMessagesList.tsx
new file mode 100644
index 0000000..1b68e49
--- /dev/null
+++ b/apps/visr/src/components/SubmittedMessagesList.tsx
@@ -0,0 +1,124 @@
+import React from "react";
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Box,
+ Divider,
+ Paper,
+ Typography,
+} from "@mui/material";
+import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
+import { Link } from "react-router-dom";
+import type {
+ SubmissionGraphQLErrorMessage,
+ SubmissionNetworkErrorMessage,
+ SubmissionSuccessMessage,
+} from "../utils/types";
+
+const renderSubmittedMessage = (
+ r:
+ | SubmissionGraphQLErrorMessage
+ | SubmissionNetworkErrorMessage
+ | SubmissionSuccessMessage,
+ i: number,
+) => {
+ switch (r.type) {
+ case "success":
+ return (
+ {}}
+ >
+
+
+ Successfully submitted{" "}
+
+ {r.message}
+
+
+
+
+ );
+
+ case "networkError":
+ return (
+
+ }>
+
+ Submission error type {r.error.name}
+
+
+
+ Submission error message {r.error.message}
+
+
+ );
+ case "graphQLError":
+ default:
+ return (
+
+ }>
+
+ Submission error type GraphQL
+
+
+
+ {r.errors.map((e, j) => {
+ return (
+
+ Error {j} {e.message}
+
+ );
+ })}
+
+
+ );
+ }
+};
+
+interface SubmittedMessagesListProps {
+ submissionResults: (
+ | SubmissionGraphQLErrorMessage
+ | SubmissionNetworkErrorMessage
+ | SubmissionSuccessMessage
+ )[];
+}
+
+const SubmittedMessagesList: React.FC = ({
+ submissionResults,
+}) => {
+ return (
+
+
+ {submissionResults.length > 0 && (
+ <>
+
+
+ Submissions
+
+
+ {submissionResults.map((r, i) => renderSubmittedMessage(r, i))}
+ >
+ )}
+
+ );
+};
+
+export default SubmittedMessagesList;
diff --git a/apps/visr/src/components/TemplateSubmissionForm.tsx b/apps/visr/src/components/TemplateSubmissionForm.tsx
new file mode 100644
index 0000000..5296979
--- /dev/null
+++ b/apps/visr/src/components/TemplateSubmissionForm.tsx
@@ -0,0 +1,112 @@
+import {
+ materialCells,
+ materialRenderers,
+} from "@jsonforms/material-renderers";
+import {
+ type JsonSchema,
+ type UISchemaElement,
+ createAjv,
+} from "@jsonforms/core";
+import { JsonForms } from "@jsonforms/react";
+import React, { useState } from "react";
+import { Divider, Snackbar, Stack, Typography, useTheme } from "@mui/material";
+import type { ErrorObject } from "ajv";
+import type { JSONObject, Visit } from "../utils/types";
+import { VisitInput } from "@diamondlightsource/sci-react-ui";
+
+interface TemplateSubmissionFormProps {
+ title: string;
+ maintainer: string;
+ repository?: string | null;
+ description?: string;
+ parametersSchema: JsonSchema;
+ parametersUISchema?: UISchemaElement;
+ prepopulatedParameters?: JSONObject;
+ visit?: Visit;
+ onSubmit: (visit: Visit, parameters: object) => void;
+}
+
+const TemplateSubmissionForm: React.FC = ({
+ title,
+ maintainer,
+ repository,
+ description,
+ parametersSchema,
+ parametersUISchema,
+ prepopulatedParameters,
+ visit,
+ onSubmit,
+}) => {
+ const theme = useTheme();
+ const validator = createAjv({ useDefaults: true, coerceTypes: true });
+
+ const [parameters, setParameters] = useState(prepopulatedParameters ?? {});
+ const [errors, setErrors] = useState([]);
+
+ const [submitted, setSubmitted] = useState(false);
+
+ const onClick = (visit: Visit, parameters?: object) => {
+ if (errors.length === 0) {
+ onSubmit(visit, parameters ?? {});
+ setSubmitted(true);
+ }
+ };
+
+ const handleCloseSnackbar = () => {
+ setSubmitted(false);
+ };
+ const formWidth =
+ (parametersUISchema?.options?.formWidth as string | undefined) ?? "100%";
+
+ return (
+
+
+ {title}
+
+
+ {description}
+
+
+ Maintainer: {maintainer}
+
+ {repository && (
+
+ Repository: {repository}
+
+ )}
+
+ {
+ setParameters(data as JSONObject);
+ setErrors(errors ? errors : []);
+ }}
+ data-testid="paramters-form"
+ />
+
+
+
+
+ );
+};
+
+export default TemplateSubmissionForm;
diff --git a/apps/visr/src/components/TemplateView.tsx b/apps/visr/src/components/TemplateView.tsx
new file mode 100644
index 0000000..c0d529b
--- /dev/null
+++ b/apps/visr/src/components/TemplateView.tsx
@@ -0,0 +1,122 @@
+import { useState } from "react";
+import { useLazyLoadQuery, useMutation } from "react-relay/hooks";
+import { graphql } from "relay-runtime";
+import type {
+ JSONObject,
+ SubmissionGraphQLErrorMessage,
+ SubmissionNetworkErrorMessage,
+ SubmissionSuccessMessage,
+} from "../utils/types";
+import { type Visit, visitToText } from "@diamondlightsource/sci-react-ui";
+import SubmissionForm from "./SubmissionForm";
+import type { TemplateViewQuery as TemplateViewQueryType } from "./__generated__/TemplateViewQuery.graphql";
+import type { TemplateViewMutation as TemplateViewMutationType } from "./__generated__/TemplateViewMutation.graphql";
+import { visitTextToVisit } from "../utils/common";
+import { Box } from "@mui/material";
+import SubmittedMessagesList from "./SubmittedMessagesList";
+
+const templateViewQuery = graphql`
+ query TemplateViewQuery($templateName: String!) {
+ workflowTemplate(name: $templateName) {
+ ...workflowTemplateFragment
+ }
+ }
+`;
+
+const templateViewMutation = graphql`
+ mutation TemplateViewMutation(
+ $templateName: String!
+ $visit: VisitInput!
+ $parameters: JSON!
+ ) {
+ submitWorkflowTemplate(
+ name: $templateName
+ visit: $visit
+ parameters: $parameters
+ ) {
+ name
+ }
+ }
+`;
+
+export default function TemplateView({
+ templateName,
+ visit,
+ prepopulatedParameters,
+}: {
+ templateName: string;
+ visit?: Visit;
+ prepopulatedParameters?: JSONObject;
+}) {
+ const data = useLazyLoadQuery(templateViewQuery, {
+ templateName,
+ });
+ const [submissionResults, setSubmissionResults] = useState<
+ (
+ | SubmissionSuccessMessage
+ | SubmissionNetworkErrorMessage
+ | SubmissionGraphQLErrorMessage
+ )[]
+ >([]);
+
+ const storedVisit = visitTextToVisit(
+ localStorage.getItem("instrumentSessionID") ?? "",
+ );
+
+ const [commitMutation] =
+ useMutation(templateViewMutation);
+
+ function submitWorkflow(visit: Visit, parameters: object) {
+ commitMutation({
+ variables: {
+ templateName: templateName,
+ visit: visit,
+ parameters: parameters,
+ },
+ onCompleted: (response, errors) => {
+ if (errors?.length) {
+ console.error("GraphQL errors:", errors);
+ setSubmissionResults(prev => [
+ {
+ type: "graphQLError",
+ errors: errors,
+ },
+ ...prev,
+ ]);
+ } else {
+ const submittedName = response.submitWorkflowTemplate.name;
+ console.log("Successfully submitted:", submittedName);
+ setSubmissionResults(prev => [
+ {
+ type: "success",
+ message: `${visitToText(visit)}/${submittedName}`,
+ },
+ ...prev,
+ ]);
+ localStorage.setItem("instrumentSessionID", visitToText(visit));
+ }
+ },
+ onError: err => {
+ console.error("Submission failed:", err);
+ setSubmissionResults(prev => [
+ {
+ type: "networkError",
+ error: err,
+ },
+ ...prev,
+ ]);
+ },
+ });
+ }
+ return (
+
+
+
+
+ );
+}
diff --git a/apps/visr/src/components/VisrNavbar.tsx b/apps/visr/src/components/VisrNavbar.tsx
new file mode 100644
index 0000000..12df262
--- /dev/null
+++ b/apps/visr/src/components/VisrNavbar.tsx
@@ -0,0 +1,48 @@
+import { Box } from "@mui/material";
+import { Link } from "react-router-dom";
+import {
+ ColourSchemeButton,
+ Navbar,
+ NavLink,
+ NavLinks,
+} from "@diamondlightsource/sci-react-ui";
+
+function VisrNavbar() {
+ return (
+
+
+
+ Spectroscopy
+
+
+ Plans
+
+
+ Workflows
+
+
+
+ }
+ rightSlot={
+
+
+
+ }
+ />
+ );
+}
+
+export default VisrNavbar;
diff --git a/apps/visr/src/components/__generated__/TemplateViewMutation.graphql.ts b/apps/visr/src/components/__generated__/TemplateViewMutation.graphql.ts
new file mode 100644
index 0000000..0a92c0c
--- /dev/null
+++ b/apps/visr/src/components/__generated__/TemplateViewMutation.graphql.ts
@@ -0,0 +1,122 @@
+/**
+ * @generated SignedSource<>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+export type VisitInput = {
+ number: number;
+ proposalCode: string;
+ proposalNumber: number;
+};
+export type TemplateViewMutation$variables = {
+ parameters: any;
+ templateName: string;
+ visit: VisitInput;
+};
+export type TemplateViewMutation$data = {
+ readonly submitWorkflowTemplate: {
+ readonly name: string;
+ };
+};
+export type TemplateViewMutation = {
+ response: TemplateViewMutation$data;
+ variables: TemplateViewMutation$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "parameters"
+},
+v1 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "templateName"
+},
+v2 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "visit"
+},
+v3 = [
+ {
+ "alias": null,
+ "args": [
+ {
+ "kind": "Variable",
+ "name": "name",
+ "variableName": "templateName"
+ },
+ {
+ "kind": "Variable",
+ "name": "parameters",
+ "variableName": "parameters"
+ },
+ {
+ "kind": "Variable",
+ "name": "visit",
+ "variableName": "visit"
+ }
+ ],
+ "concreteType": "Workflow",
+ "kind": "LinkedField",
+ "name": "submitWorkflowTemplate",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "name",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": [
+ (v0/*: any*/),
+ (v1/*: any*/),
+ (v2/*: any*/)
+ ],
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "TemplateViewMutation",
+ "selections": (v3/*: any*/),
+ "type": "Mutation",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": [
+ (v1/*: any*/),
+ (v2/*: any*/),
+ (v0/*: any*/)
+ ],
+ "kind": "Operation",
+ "name": "TemplateViewMutation",
+ "selections": (v3/*: any*/)
+ },
+ "params": {
+ "cacheID": "b9ea26832024cbe126ef60c2639577e9",
+ "id": null,
+ "metadata": {},
+ "name": "TemplateViewMutation",
+ "operationKind": "mutation",
+ "text": "mutation TemplateViewMutation(\n $templateName: String!\n $visit: VisitInput!\n $parameters: JSON!\n) {\n submitWorkflowTemplate(name: $templateName, visit: $visit, parameters: $parameters) {\n name\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "660c30e6a917a69df3bd2b5cbf6a3379";
+
+export default node;
diff --git a/apps/visr/src/components/__generated__/TemplateViewQuery.graphql.ts b/apps/visr/src/components/__generated__/TemplateViewQuery.graphql.ts
new file mode 100644
index 0000000..32f7d5a
--- /dev/null
+++ b/apps/visr/src/components/__generated__/TemplateViewQuery.graphql.ts
@@ -0,0 +1,149 @@
+/**
+ * @generated SignedSource<>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+import { FragmentRefs } from "relay-runtime";
+export type TemplateViewQuery$variables = {
+ templateName: string;
+};
+export type TemplateViewQuery$data = {
+ readonly workflowTemplate: {
+ readonly " $fragmentSpreads": FragmentRefs<"workflowTemplateFragment">;
+ };
+};
+export type TemplateViewQuery = {
+ response: TemplateViewQuery$data;
+ variables: TemplateViewQuery$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "templateName"
+ }
+],
+v1 = [
+ {
+ "kind": "Variable",
+ "name": "name",
+ "variableName": "templateName"
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "TemplateViewQuery",
+ "selections": [
+ {
+ "alias": null,
+ "args": (v1/*: any*/),
+ "concreteType": "WorkflowTemplate",
+ "kind": "LinkedField",
+ "name": "workflowTemplate",
+ "plural": false,
+ "selections": [
+ {
+ "args": null,
+ "kind": "FragmentSpread",
+ "name": "workflowTemplateFragment"
+ }
+ ],
+ "storageKey": null
+ }
+ ],
+ "type": "Query",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "TemplateViewQuery",
+ "selections": [
+ {
+ "alias": null,
+ "args": (v1/*: any*/),
+ "concreteType": "WorkflowTemplate",
+ "kind": "LinkedField",
+ "name": "workflowTemplate",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "name",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "maintainer",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "title",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "description",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "arguments",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "uiSchema",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "repository",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ]
+ },
+ "params": {
+ "cacheID": "9fb9e57ce71efced848172180274ed2a",
+ "id": null,
+ "metadata": {},
+ "name": "TemplateViewQuery",
+ "operationKind": "query",
+ "text": "query TemplateViewQuery(\n $templateName: String!\n) {\n workflowTemplate(name: $templateName) {\n ...workflowTemplateFragment\n }\n}\n\nfragment workflowTemplateFragment on WorkflowTemplate {\n name\n maintainer\n title\n description\n arguments\n uiSchema\n repository\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "907837f3f9cf4408bf964271f9a981d1";
+
+export default node;
diff --git a/apps/visr/src/components/useSpectroscopyData.ts b/apps/visr/src/components/useSpectroscopyData.ts
new file mode 100644
index 0000000..3090a3c
--- /dev/null
+++ b/apps/visr/src/components/useSpectroscopyData.ts
@@ -0,0 +1,104 @@
+import { useEffect, useRef, useState } from "react";
+import { type NDT } from "@diamondlightsource/davidia";
+
+export type RGBColour = "red" | "green" | "blue" | "gray";
+
+export type FetchMapFunction = (
+ filepath: string,
+ datapath: string,
+ colour: RGBColour,
+) => Promise;
+
+export interface DataChannels {
+ red: NDT | null;
+ green: NDT | null;
+ blue: NDT | null;
+}
+
+export interface ScanEventMessage {
+ status: "running" | "finished" | "failed";
+ filepath: string;
+ uuid: string;
+}
+
+export function useSpectroscopyData(fetchMap: FetchMapFunction) {
+ const [running, setRunning] = useState(false);
+ const [filepath, setFilepath] = useState(null);
+ const [data, setData] = useState({
+ red: null,
+ green: null,
+ blue: null,
+ });
+
+ /** Cached interval id */
+ const pollInterval = useRef | null>(null);
+
+ // Subscribe to SSE
+ useEffect(() => {
+ const evtSource = new EventSource("/api/data/events");
+
+ evtSource.onmessage = event => {
+ try {
+ const msg = JSON.parse(event.data) as ScanEventMessage;
+ console.log("SSE message:", msg);
+
+ if (msg.status === "running") {
+ setRunning(true);
+ setFilepath(msg.filepath);
+ } else if (msg.status === "finished" || msg.status === "failed") {
+ setRunning(false); // triggers final poll below
+ }
+ } catch (err) {
+ console.error("Error parsing SSE:", err);
+ }
+ };
+
+ evtSource.onerror = err => {
+ console.warn("Temporary SSE connection error:", err);
+ };
+
+ evtSource.onopen = () => {
+ console.log("SSE connection opened or re-established");
+ };
+
+ return () => evtSource.close();
+ }, []);
+
+ // Poll during scan + once more afterwards
+ useEffect(() => {
+ async function poll() {
+ if (!filepath) return;
+ try {
+ const basePath = "/entry/instrument/spectroscopy_detector/";
+ const [red, green, blue] = await Promise.all([
+ fetchMap(filepath, basePath + "RedTotal", "red"),
+ fetchMap(filepath, basePath + "GreenTotal", "green"),
+ fetchMap(filepath, basePath + "BlueTotal", "blue"),
+ ]);
+ setData({ red, green, blue });
+ } catch (err) {
+ console.error("Polling error:", err);
+ }
+ }
+
+ if (running && filepath) {
+ // start polling
+ pollInterval.current = setInterval(poll, 500); // 2 Hz
+ } else if (!running && pollInterval.current) {
+ // poll once more then clear interval
+ poll().finally(() => {
+ clearInterval(pollInterval.current!);
+ pollInterval.current = null;
+ });
+ }
+
+ return () => {
+ if (pollInterval.current) {
+ clearInterval(pollInterval.current);
+ pollInterval.current = null;
+ }
+ };
+ }, [running, filepath, fetchMap]);
+
+ return { data, running };
+}
diff --git a/apps/visr/src/context/instrumentSession/InstrumentSessionContext.ts b/apps/visr/src/context/instrumentSession/InstrumentSessionContext.ts
new file mode 100644
index 0000000..74a9c93
--- /dev/null
+++ b/apps/visr/src/context/instrumentSession/InstrumentSessionContext.ts
@@ -0,0 +1,10 @@
+import { createContext } from "react";
+
+export type InstrumentSessionContextType = {
+ instrumentSession: string;
+ setInstrumentSession: (session: string) => void;
+};
+
+export const InstrumentSessionContext = createContext<
+ InstrumentSessionContextType | undefined
+>(undefined);
diff --git a/apps/visr/src/context/instrumentSession/InstrumentSessionProvider.tsx b/apps/visr/src/context/instrumentSession/InstrumentSessionProvider.tsx
new file mode 100644
index 0000000..056918d
--- /dev/null
+++ b/apps/visr/src/context/instrumentSession/InstrumentSessionProvider.tsx
@@ -0,0 +1,28 @@
+import { useState, useEffect, type ReactNode } from "react";
+import { InstrumentSessionContext } from "./InstrumentSessionContext";
+
+const STORAGE_KEY = "instrument-session-id";
+
+export const InstrumentSessionProvider = ({
+ children,
+ defaultSessionId = "cm40661-6",
+}: {
+ children: ReactNode;
+ defaultSessionId?: string;
+}) => {
+ const [instrumentSession, setInstrumentSession] = useState(() => {
+ return localStorage.getItem(STORAGE_KEY) ?? defaultSessionId;
+ });
+
+ useEffect(() => {
+ localStorage.setItem(STORAGE_KEY, instrumentSession);
+ }, [instrumentSession]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/apps/visr/src/context/instrumentSession/useInstrumentSession.ts b/apps/visr/src/context/instrumentSession/useInstrumentSession.ts
new file mode 100644
index 0000000..2b19f86
--- /dev/null
+++ b/apps/visr/src/context/instrumentSession/useInstrumentSession.ts
@@ -0,0 +1,12 @@
+import { useContext } from "react";
+import { InstrumentSessionContext } from "./InstrumentSessionContext";
+
+export const useInstrumentSession = () => {
+ const context = useContext(InstrumentSessionContext);
+ if (!context) {
+ throw new Error(
+ "useInstrumentSession must be used within InstrumentSessionProvider",
+ );
+ }
+ return context;
+};
diff --git a/apps/visr/src/graphql/__generated__/workflowTemplateFragment.graphql.ts b/apps/visr/src/graphql/__generated__/workflowTemplateFragment.graphql.ts
new file mode 100644
index 0000000..f519396
--- /dev/null
+++ b/apps/visr/src/graphql/__generated__/workflowTemplateFragment.graphql.ts
@@ -0,0 +1,90 @@
+/**
+ * @generated SignedSource<<7eb6c59915242a110bb98268ee6f12dc>>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ReaderFragment } from 'relay-runtime';
+import { FragmentRefs } from "relay-runtime";
+export type workflowTemplateFragment$data = {
+ readonly arguments: any;
+ readonly description: string | null | undefined;
+ readonly maintainer: string;
+ readonly name: string;
+ readonly repository: string | null | undefined;
+ readonly title: string | null | undefined;
+ readonly uiSchema: any | null | undefined;
+ readonly " $fragmentType": "workflowTemplateFragment";
+};
+export type workflowTemplateFragment$key = {
+ readonly " $data"?: workflowTemplateFragment$data;
+ readonly " $fragmentSpreads": FragmentRefs<"workflowTemplateFragment">;
+};
+
+const node: ReaderFragment = {
+ "argumentDefinitions": [],
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "workflowTemplateFragment",
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "name",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "maintainer",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "title",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "description",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "arguments",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "uiSchema",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "repository",
+ "storageKey": null
+ }
+ ],
+ "type": "WorkflowTemplate",
+ "abstractKey": null
+};
+
+(node as any).hash = "959850317e316a423e70b0c89c011f27";
+
+export default node;
diff --git a/apps/visr/src/graphql/__generated__/workflowsQuery.graphql.ts b/apps/visr/src/graphql/__generated__/workflowsQuery.graphql.ts
new file mode 100644
index 0000000..d55fe50
--- /dev/null
+++ b/apps/visr/src/graphql/__generated__/workflowsQuery.graphql.ts
@@ -0,0 +1,205 @@
+/**
+ * @generated SignedSource<>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+export type VisitInput = {
+ number: number;
+ proposalCode: string;
+ proposalNumber: number;
+};
+export type WorkflowFilter = {
+ creator?: any | null | undefined;
+ template?: any | null | undefined;
+ workflowStatusFilter?: WorkflowStatusFilter | null | undefined;
+};
+export type WorkflowStatusFilter = {
+ error?: boolean;
+ failed?: boolean;
+ pending?: boolean;
+ running?: boolean;
+ succeeded?: boolean;
+};
+export type workflowsQuery$variables = {
+ cursor?: string | null | undefined;
+ filter?: WorkflowFilter | null | undefined;
+ limit: number;
+ visit: VisitInput;
+};
+export type workflowsQuery$data = {
+ readonly workflows: {
+ readonly nodes: ReadonlyArray<{
+ readonly name: string;
+ }>;
+ readonly pageInfo: {
+ readonly endCursor: string | null | undefined;
+ readonly hasNextPage: boolean;
+ readonly hasPreviousPage: boolean;
+ readonly startCursor: string | null | undefined;
+ };
+ };
+};
+export type workflowsQuery = {
+ response: workflowsQuery$data;
+ variables: workflowsQuery$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "cursor"
+},
+v1 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "filter"
+},
+v2 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "limit"
+},
+v3 = {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "visit"
+},
+v4 = [
+ {
+ "alias": null,
+ "args": [
+ {
+ "kind": "Variable",
+ "name": "cursor",
+ "variableName": "cursor"
+ },
+ {
+ "kind": "Variable",
+ "name": "filter",
+ "variableName": "filter"
+ },
+ {
+ "kind": "Variable",
+ "name": "limit",
+ "variableName": "limit"
+ },
+ {
+ "kind": "Variable",
+ "name": "visit",
+ "variableName": "visit"
+ }
+ ],
+ "concreteType": "WorkflowConnection",
+ "kind": "LinkedField",
+ "name": "workflows",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "Workflow",
+ "kind": "LinkedField",
+ "name": "nodes",
+ "plural": true,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "name",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "PageInfo",
+ "kind": "LinkedField",
+ "name": "pageInfo",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "endCursor",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "hasNextPage",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "hasPreviousPage",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "startCursor",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": [
+ (v0/*: any*/),
+ (v1/*: any*/),
+ (v2/*: any*/),
+ (v3/*: any*/)
+ ],
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "workflowsQuery",
+ "selections": (v4/*: any*/),
+ "type": "Query",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": [
+ (v3/*: any*/),
+ (v0/*: any*/),
+ (v2/*: any*/),
+ (v1/*: any*/)
+ ],
+ "kind": "Operation",
+ "name": "workflowsQuery",
+ "selections": (v4/*: any*/)
+ },
+ "params": {
+ "cacheID": "6e6da56e134de097d20cafd8abe7e0aa",
+ "id": null,
+ "metadata": {},
+ "name": "workflowsQuery",
+ "operationKind": "query",
+ "text": "query workflowsQuery(\n $visit: VisitInput!\n $cursor: String\n $limit: Int!\n $filter: WorkflowFilter\n) {\n workflows(visit: $visit, cursor: $cursor, limit: $limit, filter: $filter) {\n nodes {\n name\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "7451d3fab389359463e7424e2fcaa8d0";
+
+export default node;
diff --git a/apps/visr/src/graphql/workflowTemplateFragment.ts b/apps/visr/src/graphql/workflowTemplateFragment.ts
new file mode 100644
index 0000000..28db243
--- /dev/null
+++ b/apps/visr/src/graphql/workflowTemplateFragment.ts
@@ -0,0 +1,13 @@
+import { graphql } from "react-relay";
+
+export const workflowTemplateFragment = graphql`
+ fragment workflowTemplateFragment on WorkflowTemplate {
+ name
+ maintainer
+ title
+ description
+ arguments
+ uiSchema
+ repository
+ }
+`;
diff --git a/apps/visr/src/graphql/workflowsQuery.ts b/apps/visr/src/graphql/workflowsQuery.ts
new file mode 100644
index 0000000..e194444
--- /dev/null
+++ b/apps/visr/src/graphql/workflowsQuery.ts
@@ -0,0 +1,22 @@
+import { graphql } from "relay-runtime";
+
+export const workflowsQuery = graphql`
+ query workflowsQuery(
+ $visit: VisitInput!
+ $cursor: String
+ $limit: Int!
+ $filter: WorkflowFilter
+ ) {
+ workflows(visit: $visit, cursor: $cursor, limit: $limit, filter: $filter) {
+ nodes {
+ name
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ }
+ }
+ }
+`;
diff --git a/apps/visr/src/main.tsx b/apps/visr/src/main.tsx
new file mode 100644
index 0000000..e29eeb5
--- /dev/null
+++ b/apps/visr/src/main.tsx
@@ -0,0 +1,67 @@
+import { DiamondTheme, ThemeProvider } from "@diamondlightsource/sci-react-ui";
+import { RouterProvider, createBrowserRouter } from "react-router-dom";
+
+import Dashboard from "./routes/Dashboard.tsx";
+import { InstrumentSessionProvider } from "./context/instrumentSession/InstrumentSessionProvider.tsx";
+import JsonFormsPlans from "./routes/Plans.tsx";
+import { Layout } from "./routes/Layout.tsx";
+import Spectroscopy from "./routes/Spectroscopy.tsx";
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+declare global {
+ interface Window {
+ global?: typeof globalThis;
+ }
+}
+
+window.global ||= window;
+import Workflows from "./routes/Workflows.tsx";
+import { RelayEnvironmentProvider } from "react-relay";
+import { RelayEnvironment } from "./RelayEnvironment.ts";
+
+async function enableMocking() {
+ if (import.meta.env.DEV) {
+ const { worker } = await import("./mocks/browser");
+ return worker.start();
+ }
+}
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ children: [
+ {
+ index: true,
+ element: ,
+ },
+ {
+ path: "spectroscopy",
+ element: ,
+ },
+ {
+ path: "plans",
+ element: ,
+ },
+ {
+ path: "/workflows",
+ element: ,
+ },
+ ],
+ },
+]);
+
+enableMocking().then(() => {
+ createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+
+
+
+ ,
+ );
+});
diff --git a/apps/visr/src/mocks/browser.ts b/apps/visr/src/mocks/browser.ts
new file mode 100644
index 0000000..bcd82e4
--- /dev/null
+++ b/apps/visr/src/mocks/browser.ts
@@ -0,0 +1,4 @@
+import { setupWorker } from "msw/browser";
+import { handlers } from "./handlers";
+
+export const worker = setupWorker(...handlers);
diff --git a/apps/visr/src/mocks/handlers.ts b/apps/visr/src/mocks/handlers.ts
new file mode 100644
index 0000000..b716c96
--- /dev/null
+++ b/apps/visr/src/mocks/handlers.ts
@@ -0,0 +1,122 @@
+import { http, HttpResponse } from "msw";
+import workflowsResponse from "./workflows-response.json";
+import plansResponse from "./plans-response.json";
+import instrumentSessionsResponse from "./instrumentSessions-response.json";
+import type { ScanEventMessage } from "../components/useSpectroscopyData";
+
+const fakeTaskId = "7304e8e0-81c6-4978-9a9d-9046ab79ce3c";
+
+function mapData(): (number | null)[][] {
+ return [
+ [
+ 5467227.0, 5467227.0, 5480663.0, 5478486.0, 5477020.0, 5474645.0,
+ 5472603.0, 5470330.0, 5468827.0, 5467346.0,
+ ],
+ [
+ 5465947.0, 5464940.0, 5483401.0, 5480384.0, 5477378.0, 5474776.0,
+ 5471462.0, 5450634.0, 5454651.0, 5465208.0,
+ ],
+ [
+ 5463907.0, 5463469.0, 5481975.0, 5479338.0, 5476649.0, 5474993.0,
+ 5473529.0, 5471431.0, 5470030.0, 5468721.0,
+ ],
+ [
+ 5466907.0, 5466023.0, 5483679.0, 5480875.0, 5478044.0, 5475180.0,
+ 5473196.0, 5471168.0, 5469539.0, 5468190.0,
+ ],
+ [
+ 5466626.0, 5465494.0, 5483503.0, 5480586.0, 5477659.0, 5475069.0,
+ 5473287.0, 5471380.0, 5469737.0, 5468496.0,
+ ],
+ [
+ 5466713.0, 5465920.0, 5483587.0, 5480582.0, 5477829.0, 5475202.0,
+ 5473244.0, 5471103.0, 5468010.0, 5468222.0,
+ ],
+ [5467700.0, null, null, null, null, null, null, null, null, null],
+ [null, null, null, null, null, null, null, null, null, null],
+ [null, null, null, null, null, null, null, null, null, null],
+ [null, null, null, null, null, null, null, null, null, null],
+ ];
+}
+
+export const handlers = [
+ http.post("/api/graphql", request => {
+ const referrer = request.request.referrer;
+ if (referrer.search("workflows") > 0) {
+ return HttpResponse.json(workflowsResponse);
+ } else {
+ return HttpResponse.json(instrumentSessionsResponse);
+ }
+ }),
+
+ http.get("/api/plans", () => {
+ return HttpResponse.json(plansResponse);
+ }),
+
+ http.put("/api/worker/task", () => {
+ return HttpResponse.json({
+ task_id: fakeTaskId,
+ });
+ }),
+
+ http.post("/api/tasks", () => {
+ return HttpResponse.json({
+ task_id: fakeTaskId,
+ });
+ }),
+
+ http.get("/api/data/map", ({ request }) => {
+ const url = new URL(request.url);
+ const filepath = url.searchParams.get("filepath");
+ const datapath = url.searchParams.get("datapath");
+ console.log("Mock /api/data/map called", { filepath, datapath });
+ const data = mapData();
+ return HttpResponse.json({ values: data });
+ }),
+
+ http.get("/api/data/events", async () => {
+ const encoder = new TextEncoder();
+
+ // Create a ReadableStream that emits fake scan events
+ const stream = new ReadableStream({
+ start(controller) {
+ const send = (event: ScanEventMessage) => {
+ controller.enqueue(
+ encoder.encode(`data: ${JSON.stringify(event)}\n\n`),
+ );
+ };
+
+ // scan starts
+ send({
+ uuid: "fake-scan-uuid",
+ filepath: "/mock/path/fake.nxs",
+ status: "running",
+ });
+
+ // simulate data collection for ~5 seconds
+ let counter = 0;
+ const interval = setInterval(() => {
+ counter++;
+ console.log("Mock event tick", counter);
+ if (counter >= 25) {
+ clearInterval(interval);
+ // Scan stops
+ send({
+ uuid: "fake-scan-uuid",
+ filepath: "/mock/path/fake.nxs",
+ status: "finished",
+ });
+ }
+ }, 200);
+ },
+ });
+
+ return new HttpResponse(stream, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ Connection: "keep-alive",
+ },
+ });
+ }),
+];
diff --git a/apps/visr/src/mocks/instrumentSessions-response.json b/apps/visr/src/mocks/instrumentSessions-response.json
new file mode 100644
index 0000000..c4629c7
--- /dev/null
+++ b/apps/visr/src/mocks/instrumentSessions-response.json
@@ -0,0 +1,71 @@
+{
+ "data": {
+ "instrument": {
+ "instrumentSessions": [
+ {
+ "instrumentSessionNumber": 1,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 39125
+ }
+ },
+ {
+ "instrumentSessionNumber": 2,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 39125
+ }
+ },
+ {
+ "instrumentSessionNumber": 3,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 39125
+ }
+ },
+ {
+ "instrumentSessionNumber": 1,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ },
+ {
+ "instrumentSessionNumber": 2,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ },
+ {
+ "instrumentSessionNumber": 3,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ },
+ {
+ "instrumentSessionNumber": 4,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ },
+ {
+ "instrumentSessionNumber": 5,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ },
+ {
+ "instrumentSessionNumber": 6,
+ "proposal": {
+ "proposalCategory": "CM",
+ "proposalNumber": 40661
+ }
+ }
+ ]
+ }
+ }
+}
diff --git a/apps/visr/src/mocks/plans-response.json b/apps/visr/src/mocks/plans-response.json
new file mode 100644
index 0000000..ee809a5
--- /dev/null
+++ b/apps/visr/src/mocks/plans-response.json
@@ -0,0 +1,868 @@
+{
+ "plans": [
+ {
+ "name": "hi_plan",
+ "description": "Says hi to you",
+ "schema": {
+ "properties": { "name": { "title": "Name", "type": "string" } }
+ }
+ },
+ {
+ "name": "count",
+ "description": "Reads from a number of devices.\n Wraps bluesky.plans.count(det, num, delay, md=metadata) exposing only serializable\n parameters and metadata.",
+ "schema": {
+ "additionalProperties": false,
+ "properties": {
+ "detectors": {
+ "items": {
+ "enum": [
+ "sample_stage",
+ "sample_det",
+ "oav",
+ "synchrotron",
+ "panda"
+ ],
+ "type": "bluesky.protocols.Readable"
+ },
+ "title": "Detectors",
+ "type": "array",
+ "uniqueItems": true
+ },
+ "num": {
+ "title": "Num",
+ "type": "integer"
+ },
+ "delay": {
+ "anyOf": [
+ {
+ "type": "number"
+ },
+ {
+ "items": {
+ "type": "number"
+ },
+ "type": "array"
+ }
+ ],
+ "title": "Delay"
+ },
+ "metadata": {
+ "title": "Metadata",
+ "type": "object"
+ }
+ },
+ "required": ["detectors"],
+ "title": "count",
+ "type": "object"
+ }
+ },
+ {
+ "name": "spec_scan",
+ "description": "Generic plan for reading `detectors` at every point of a ScanSpec `Spec`.\n A `Spec` is an N-dimensional path.\n ",
+ "schema": {
+ "$defs": {
+ "Circle_Reference_": {
+ "additionalProperties": false,
+ "description": "Mask contains points of axis within an xy circle of given radius.\n\n.. example_spec::\n\n from scanspec.regions import Circle\n from scanspec.specs import Line\n\n grid = Line(\"y\", 1, 3, 10) * ~Line(\"x\", 0, 2, 10)\n spec = grid & Circle(\"x\", \"y\", 1, 2, 0.9)",
+ "properties": {
+ "x_axis": {
+ "description": "The name matching the x axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "X Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "y_axis": {
+ "description": "The name matching the y axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Y Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "x_middle": {
+ "description": "The central x point of the circle",
+ "title": "X Middle",
+ "type": "number"
+ },
+ "y_middle": {
+ "description": "The central y point of the circle",
+ "title": "Y Middle",
+ "type": "number"
+ },
+ "radius": {
+ "description": "Radius of the circle",
+ "exclusiveMinimum": 0,
+ "title": "Radius",
+ "type": "number"
+ },
+ "type": {
+ "const": "Circle",
+ "default": "Circle",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["x_axis", "y_axis", "x_middle", "y_middle", "radius"],
+ "title": "Circle",
+ "type": "object"
+ },
+ "CombinationOf_Reference_": {
+ "additionalProperties": false,
+ "description": "Abstract baseclass for a combination of two regions, left and right.",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Region",
+ "description": "The left-hand Region to combine"
+ },
+ "right": {
+ "$ref": "#/$defs/Region",
+ "description": "The right-hand Region to combine"
+ },
+ "type": {
+ "const": "CombinationOf",
+ "default": "CombinationOf",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "CombinationOf",
+ "type": "object"
+ },
+ "Concat_Reference_": {
+ "additionalProperties": false,
+ "description": "Concatenate two Specs together, running one after the other.\n\nEach Dimension of left and right must contain the same axes. Typically\nformed using `Spec.concat`.\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = Line(\"x\", 1, 3, 3).concat(Line(\"x\", 4, 5, 5))",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Spec",
+ "description": "The left-hand Spec to Concat, midpoints will appear earlier"
+ },
+ "right": {
+ "$ref": "#/$defs/Spec",
+ "description": "The right-hand Spec to Concat, midpoints will appear later"
+ },
+ "gap": {
+ "default": false,
+ "description": "If True, force a gap in the output at the join",
+ "title": "Gap",
+ "type": "boolean"
+ },
+ "check_path_changes": {
+ "default": true,
+ "description": "If True path through scan will not be modified by squash",
+ "title": "Check Path Changes",
+ "type": "boolean"
+ },
+ "type": {
+ "const": "Concat",
+ "default": "Concat",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "Concat",
+ "type": "object"
+ },
+ "DifferenceOf_Reference_": {
+ "additionalProperties": false,
+ "description": "A point is in DifferenceOf(a, b) if in a and not in b.\n\nTypically created with the ``-`` operator.\n\n>>> r = Range(\"x\", 0.5, 2.5) - Range(\"x\", 1.5, 3.5)\n>>> r.mask({\"x\": np.array([0, 1, 2, 3, 4])})\narray([False, True, False, False, False])",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Region",
+ "description": "The left-hand Region to combine"
+ },
+ "right": {
+ "$ref": "#/$defs/Region",
+ "description": "The right-hand Region to combine"
+ },
+ "type": {
+ "const": "DifferenceOf",
+ "default": "DifferenceOf",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "DifferenceOf",
+ "type": "object"
+ },
+ "Ellipse_Reference_": {
+ "additionalProperties": false,
+ "description": "Mask contains points of axis within an xy ellipse of given radius.\n\n.. example_spec::\n\n from scanspec.regions import Ellipse\n from scanspec.specs import Line\n\n grid = Line(\"y\", 3, 8, 10) * ~Line(\"x\", 1 ,8, 10)\n spec = grid & Ellipse(\"x\", \"y\", 5, 5, 2, 3, 75)",
+ "properties": {
+ "x_axis": {
+ "description": "The name matching the x axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "X Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "y_axis": {
+ "description": "The name matching the y axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Y Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "x_middle": {
+ "description": "The central x point of the ellipse",
+ "title": "X Middle",
+ "type": "number"
+ },
+ "y_middle": {
+ "description": "The central y point of the ellipse",
+ "title": "Y Middle",
+ "type": "number"
+ },
+ "x_radius": {
+ "description": "The radius along the x axis of the ellipse",
+ "exclusiveMinimum": 0,
+ "title": "X Radius",
+ "type": "number"
+ },
+ "y_radius": {
+ "description": "The radius along the y axis of the ellipse",
+ "exclusiveMinimum": 0,
+ "title": "Y Radius",
+ "type": "number"
+ },
+ "angle": {
+ "default": 0,
+ "description": "The angle of the ellipse (degrees)",
+ "title": "Angle",
+ "type": "number"
+ },
+ "type": {
+ "const": "Ellipse",
+ "default": "Ellipse",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "x_axis",
+ "y_axis",
+ "x_middle",
+ "y_middle",
+ "x_radius",
+ "y_radius"
+ ],
+ "title": "Ellipse",
+ "type": "object"
+ },
+ "IntersectionOf_Reference_": {
+ "additionalProperties": false,
+ "description": "A point is in IntersectionOf(a, b) if in both a and b.\n\nTypically created with the ``&`` operator.\n\n>>> r = Range(\"x\", 0.5, 2.5) & Range(\"x\", 1.5, 3.5)\n>>> r.mask({\"x\": np.array([0, 1, 2, 3, 4])})\narray([False, False, True, False, False])",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Region",
+ "description": "The left-hand Region to combine"
+ },
+ "right": {
+ "$ref": "#/$defs/Region",
+ "description": "The right-hand Region to combine"
+ },
+ "type": {
+ "const": "IntersectionOf",
+ "default": "IntersectionOf",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "IntersectionOf",
+ "type": "object"
+ },
+ "Line_Reference_": {
+ "additionalProperties": false,
+ "description": "Linearly spaced frames with start and stop as first and last midpoints.\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = Line(\"x\", 1, 2, 5)",
+ "properties": {
+ "axis": {
+ "description": "An identifier for what to move",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "start": {
+ "description": "Midpoint of the first point of the line",
+ "title": "Start",
+ "type": "number"
+ },
+ "stop": {
+ "description": "Midpoint of the last point of the line",
+ "title": "Stop",
+ "type": "number"
+ },
+ "num": {
+ "description": "Number of frames to produce",
+ "minimum": 1,
+ "title": "Num",
+ "type": "integer"
+ },
+ "type": {
+ "const": "Line",
+ "default": "Line",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["axis", "start", "stop", "num"],
+ "title": "Line",
+ "type": "object"
+ },
+ "Mask_Reference_": {
+ "additionalProperties": false,
+ "description": "Restrict Spec to only midpoints that fall inside the given Region.\n\nTypically created with the ``&`` operator. It also pushes down the\n``& | ^ -`` operators to its `Region` to avoid the need for brackets on\ncombinations of Regions.\n\nIf a Region spans multiple Frames objects, they will be squashed together.\n\n.. example_spec::\n\n from scanspec.regions import Circle\n from scanspec.specs import Line\n\n spec = Line(\"y\", 1, 3, 3) * Line(\"x\", 3, 5, 5) & Circle(\"x\", \"y\", 4, 2, 1.2)\n\nSee Also: `why-squash-can-change-path`",
+ "properties": {
+ "spec": {
+ "$ref": "#/$defs/Spec",
+ "description": "The Spec containing the source midpoints"
+ },
+ "region": {
+ "$ref": "#/$defs/Region",
+ "description": "The Region that midpoints will be inside"
+ },
+ "check_path_changes": {
+ "default": true,
+ "description": "If True path through scan will not be modified by squash",
+ "title": "Check Path Changes",
+ "type": "boolean"
+ },
+ "type": {
+ "const": "Mask",
+ "default": "Mask",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["spec", "region"],
+ "title": "Mask",
+ "type": "object"
+ },
+ "Polygon_Reference_": {
+ "additionalProperties": false,
+ "description": "Mask contains points of axis within a rotated xy polygon.\n\n.. example_spec::\n\n from scanspec.regions import Polygon\n from scanspec.specs import Line\n\n grid = Line(\"y\", 3, 8, 10) * ~Line(\"x\", 1 ,8, 10)\n spec = grid & Polygon(\"x\", \"y\", [1.0, 6.0, 8.0, 2.0], [4.0, 10.0, 6.0, 1.0])",
+ "properties": {
+ "x_axis": {
+ "description": "The name matching the x axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "X Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "y_axis": {
+ "description": "The name matching the y axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Y Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "x_verts": {
+ "description": "The Nx1 x coordinates of the polygons vertices",
+ "items": {
+ "type": "number"
+ },
+ "minItems": 3,
+ "title": "X Verts",
+ "type": "array"
+ },
+ "y_verts": {
+ "description": "The Nx1 y coordinates of the polygons vertices",
+ "items": {
+ "type": "number"
+ },
+ "minItems": 3,
+ "title": "Y Verts",
+ "type": "array"
+ },
+ "type": {
+ "const": "Polygon",
+ "default": "Polygon",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["x_axis", "y_axis", "x_verts", "y_verts"],
+ "title": "Polygon",
+ "type": "object"
+ },
+ "Product_Reference_": {
+ "additionalProperties": false,
+ "description": "Outer product of two Specs, nesting inner within outer.\n\nThis means that inner will run in its entirety at each point in outer.\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = Line(\"y\", 1, 2, 3) * Line(\"x\", 3, 4, 12)",
+ "properties": {
+ "outer": {
+ "$ref": "#/$defs/Spec",
+ "description": "Will be executed once"
+ },
+ "inner": {
+ "$ref": "#/$defs/Spec",
+ "description": "Will be executed len(outer) times"
+ },
+ "type": {
+ "const": "Product",
+ "default": "Product",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["outer", "inner"],
+ "title": "Product",
+ "type": "object"
+ },
+ "Range_Reference_": {
+ "additionalProperties": false,
+ "description": "Mask contains points of axis >= min and <= max.\n\n>>> r = Range(\"x\", 1, 2)\n>>> r.mask({\"x\": np.array([0, 1, 2, 3, 4])})\narray([False, True, True, False, False])",
+ "properties": {
+ "axis": {
+ "description": "The name matching the axis to mask in spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "min": {
+ "description": "The minimum inclusive value in the region",
+ "title": "Min",
+ "type": "number"
+ },
+ "max": {
+ "description": "The minimum inclusive value in the region",
+ "title": "Max",
+ "type": "number"
+ },
+ "type": {
+ "const": "Range",
+ "default": "Range",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["axis", "min", "max"],
+ "title": "Range",
+ "type": "object"
+ },
+ "Rectangle_Reference_": {
+ "additionalProperties": false,
+ "description": "Mask contains points of axis within a rotated xy rectangle.\n\n.. example_spec::\n\n from scanspec.regions import Rectangle\n from scanspec.specs import Line\n\n grid = Line(\"y\", 1, 3, 10) * ~Line(\"x\", 0, 2, 10)\n spec = grid & Rectangle(\"x\", \"y\", 0, 1.1, 1.5, 2.1, 30)",
+ "properties": {
+ "x_axis": {
+ "description": "The name matching the x axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "X Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "y_axis": {
+ "description": "The name matching the y axis of the spec",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Y Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "x_min": {
+ "description": "Minimum inclusive x value in the region",
+ "title": "X Min",
+ "type": "number"
+ },
+ "y_min": {
+ "description": "Minimum inclusive y value in the region",
+ "title": "Y Min",
+ "type": "number"
+ },
+ "x_max": {
+ "description": "Maximum inclusive x value in the region",
+ "title": "X Max",
+ "type": "number"
+ },
+ "y_max": {
+ "description": "Maximum inclusive y value in the region",
+ "title": "Y Max",
+ "type": "number"
+ },
+ "angle": {
+ "default": 0,
+ "description": "Clockwise rotation angle of the rectangle",
+ "title": "Angle",
+ "type": "number"
+ },
+ "type": {
+ "const": "Rectangle",
+ "default": "Rectangle",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "x_axis",
+ "y_axis",
+ "x_min",
+ "y_min",
+ "x_max",
+ "y_max"
+ ],
+ "title": "Rectangle",
+ "type": "object"
+ },
+ "Region": {
+ "discriminator": {
+ "mapping": {
+ "Circle": "#/$defs/Circle_Reference_",
+ "CombinationOf": "#/$defs/CombinationOf_Reference_",
+ "DifferenceOf": "#/$defs/DifferenceOf_Reference_",
+ "Ellipse": "#/$defs/Ellipse_Reference_",
+ "IntersectionOf": "#/$defs/IntersectionOf_Reference_",
+ "Polygon": "#/$defs/Polygon_Reference_",
+ "Range": "#/$defs/Range_Reference_",
+ "Rectangle": "#/$defs/Rectangle_Reference_",
+ "SymmetricDifferenceOf": "#/$defs/SymmetricDifferenceOf_Reference_",
+ "UnionOf": "#/$defs/UnionOf_Reference_"
+ },
+ "propertyName": "type"
+ },
+ "oneOf": [
+ {
+ "$ref": "#/$defs/Range_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Rectangle_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Polygon_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Circle_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Ellipse_Reference_"
+ },
+ {
+ "$ref": "#/$defs/CombinationOf_Reference_"
+ },
+ {
+ "$ref": "#/$defs/UnionOf_Reference_"
+ },
+ {
+ "$ref": "#/$defs/IntersectionOf_Reference_"
+ },
+ {
+ "$ref": "#/$defs/DifferenceOf_Reference_"
+ },
+ {
+ "$ref": "#/$defs/SymmetricDifferenceOf_Reference_"
+ }
+ ]
+ },
+ "Repeat_Reference_": {
+ "additionalProperties": false,
+ "description": "Repeat an empty frame num times.\n\nCan be used on the outside of a scan to repeat the same scan many times.\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = 2 * ~Line.bounded(\"x\", 3, 4, 1)\n\nIf you want snaked axes to have no gap between iterations you can do:\n\n.. example_spec::\n\n from scanspec.specs import Line, Repeat\n\n spec = Repeat(2, gap=False) * ~Line.bounded(\"x\", 3, 4, 1)\n\n.. note:: There is no turnaround arrow at x=4",
+ "properties": {
+ "num": {
+ "description": "Number of frames to produce",
+ "minimum": 1,
+ "title": "Num",
+ "type": "integer"
+ },
+ "gap": {
+ "default": true,
+ "description": "If False and the slowest of the stack of Frames is snaked then the end and start of consecutive iterations of Spec will have no gap",
+ "title": "Gap",
+ "type": "boolean"
+ },
+ "type": {
+ "const": "Repeat",
+ "default": "Repeat",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["num"],
+ "title": "Repeat",
+ "type": "object"
+ },
+ "Snake_Reference_": {
+ "additionalProperties": false,
+ "description": "Run the Spec in reverse on every other iteration when nested.\n\nTypically created with the ``~`` operator.\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = Line(\"y\", 1, 3, 3) * ~Line(\"x\", 3, 5, 5)",
+ "properties": {
+ "spec": {
+ "$ref": "#/$defs/Spec",
+ "description": "The Spec to run in reverse every other iteration"
+ },
+ "type": {
+ "const": "Snake",
+ "default": "Snake",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["spec"],
+ "title": "Snake",
+ "type": "object"
+ },
+ "Spec": {
+ "discriminator": {
+ "mapping": {
+ "Concat": "#/$defs/Concat_Reference_",
+ "Line": "#/$defs/Line_Reference_",
+ "Mask": "#/$defs/Mask_Reference_",
+ "Product": "#/$defs/Product_Reference_",
+ "Repeat": "#/$defs/Repeat_Reference_",
+ "Snake": "#/$defs/Snake_Reference_",
+ "Spiral": "#/$defs/Spiral_Reference_",
+ "Squash": "#/$defs/Squash_Reference_",
+ "Static": "#/$defs/Static_Reference_",
+ "Zip": "#/$defs/Zip_Reference_"
+ },
+ "propertyName": "type"
+ },
+ "oneOf": [
+ {
+ "$ref": "#/$defs/Line_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Static_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Squash_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Spiral_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Product_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Repeat_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Zip_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Snake_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Concat_Reference_"
+ },
+ {
+ "$ref": "#/$defs/Mask_Reference_"
+ }
+ ]
+ },
+ "Spiral_Reference_": {
+ "additionalProperties": false,
+ "description": "Archimedean spiral of \"x_axis\" and \"y_axis\".\n\nStarts at centre point (\"x_start\", \"y_start\") with angle \"rotate\". Produces\n\"num\" points in a spiral spanning width of \"x_range\" and height of \"y_range\"\n\n.. example_spec::\n\n from scanspec.specs import Spiral\n\n spec = Spiral(\"x\", \"y\", 1, 5, 10, 50, 30)",
+ "properties": {
+ "x_axis": {
+ "description": "An identifier for what to move for x",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "X Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "y_axis": {
+ "description": "An identifier for what to move for y",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Y Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "x_start": {
+ "description": "x centre of the spiral",
+ "title": "X Start",
+ "type": "number"
+ },
+ "y_start": {
+ "description": "y centre of the spiral",
+ "title": "Y Start",
+ "type": "number"
+ },
+ "x_range": {
+ "description": "x width of the spiral",
+ "title": "X Range",
+ "type": "number"
+ },
+ "y_range": {
+ "description": "y width of the spiral",
+ "title": "Y Range",
+ "type": "number"
+ },
+ "num": {
+ "description": "Number of frames to produce",
+ "minimum": 1,
+ "title": "Num",
+ "type": "integer"
+ },
+ "rotate": {
+ "default": 0,
+ "description": "How much to rotate the angle of the spiral",
+ "title": "Rotate",
+ "type": "number"
+ },
+ "type": {
+ "const": "Spiral",
+ "default": "Spiral",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": [
+ "x_axis",
+ "y_axis",
+ "x_start",
+ "y_start",
+ "x_range",
+ "y_range",
+ "num"
+ ],
+ "title": "Spiral",
+ "type": "object"
+ },
+ "Squash_Reference_": {
+ "additionalProperties": false,
+ "description": "Squash a stack of Frames together into a single expanded Frames object.\n\nSee Also:\n `why-squash-can-change-path`\n\n.. example_spec::\n\n from scanspec.specs import Line, Squash\n\n spec = Squash(Line(\"y\", 1, 2, 3) * Line(\"x\", 0, 1, 4))",
+ "properties": {
+ "spec": {
+ "$ref": "#/$defs/Spec",
+ "description": "The Spec to squash the dimensions of"
+ },
+ "check_path_changes": {
+ "default": true,
+ "description": "If True path through scan will not be modified by squash",
+ "title": "Check Path Changes",
+ "type": "boolean"
+ },
+ "type": {
+ "const": "Squash",
+ "default": "Squash",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["spec"],
+ "title": "Squash",
+ "type": "object"
+ },
+ "Static_Reference_": {
+ "additionalProperties": false,
+ "description": "A static frame, repeated num times, with axis at value.\n\nCan be used to set axis=value at every point in a scan.\n\n.. example_spec::\n\n from scanspec.specs import Line, Static\n\n spec = Line(\"y\", 1, 2, 3).zip(Static(\"x\", 3))",
+ "properties": {
+ "axis": {
+ "description": "An identifier for what to move",
+ "enum": ["sample_stage.x", "sample_stage.y", "sample_stage.z"],
+ "title": "Axis",
+ "type": "bluesky.protocols.Movable"
+ },
+ "value": {
+ "description": "The value at each point",
+ "title": "Value",
+ "type": "number"
+ },
+ "num": {
+ "default": 1,
+ "description": "Number of frames to produce",
+ "minimum": 1,
+ "title": "Num",
+ "type": "integer"
+ },
+ "type": {
+ "const": "Static",
+ "default": "Static",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["axis", "value"],
+ "title": "Static",
+ "type": "object"
+ },
+ "SymmetricDifferenceOf_Reference_": {
+ "additionalProperties": false,
+ "description": "A point is in SymmetricDifferenceOf(a, b) if in either a or b, but not both.\n\nTypically created with the ``^`` operator.\n\n>>> r = Range(\"x\", 0.5, 2.5) ^ Range(\"x\", 1.5, 3.5)\n>>> r.mask({\"x\": np.array([0, 1, 2, 3, 4])})\narray([False, True, False, True, False])",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Region",
+ "description": "The left-hand Region to combine"
+ },
+ "right": {
+ "$ref": "#/$defs/Region",
+ "description": "The right-hand Region to combine"
+ },
+ "type": {
+ "const": "SymmetricDifferenceOf",
+ "default": "SymmetricDifferenceOf",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "SymmetricDifferenceOf",
+ "type": "object"
+ },
+ "UnionOf_Reference_": {
+ "additionalProperties": false,
+ "description": "A point is in UnionOf(a, b) if in either a or b.\n\nTypically created with the ``|`` operator\n\n>>> r = Range(\"x\", 0.5, 2.5) | Range(\"x\", 1.5, 3.5)\n>>> r.mask({\"x\": np.array([0, 1, 2, 3, 4])})\narray([False, True, True, True, False])",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Region",
+ "description": "The left-hand Region to combine"
+ },
+ "right": {
+ "$ref": "#/$defs/Region",
+ "description": "The right-hand Region to combine"
+ },
+ "type": {
+ "const": "UnionOf",
+ "default": "UnionOf",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "UnionOf",
+ "type": "object"
+ },
+ "Zip_Reference_": {
+ "additionalProperties": false,
+ "description": "Run two Specs in parallel, merging their midpoints together.\n\nTypically formed using `Spec.zip`.\n\nStacks of Frames are merged by:\n\n- If right creates a stack of a single Frames object of size 1, expand it to\n the size of the fastest Frames object created by left\n- Merge individual Frames objects together from fastest to slowest\n\nThis means that Zipping a Spec producing stack [l2, l1] with a Spec\nproducing stack [r1] will assert len(l1)==len(r1), and produce\nstack [l2, l1.zip(r1)].\n\n.. example_spec::\n\n from scanspec.specs import Line\n\n spec = Line(\"z\", 1, 2, 3) * Line(\"y\", 3, 4, 5).zip(Line(\"x\", 4, 5, 5))",
+ "properties": {
+ "left": {
+ "$ref": "#/$defs/Spec",
+ "description": "The left-hand Spec to Zip, will appear earlier in axes"
+ },
+ "right": {
+ "$ref": "#/$defs/Spec",
+ "description": "The right-hand Spec to Zip, will appear later in axes"
+ },
+ "type": {
+ "const": "Zip",
+ "default": "Zip",
+ "title": "Type",
+ "type": "string"
+ }
+ },
+ "required": ["left", "right"],
+ "title": "Zip",
+ "type": "object"
+ }
+ },
+ "additionalProperties": false,
+ "properties": {
+ "detectors": {
+ "items": {
+ "enum": [
+ "sample_stage",
+ "sample_det",
+ "oav",
+ "synchrotron",
+ "panda"
+ ],
+ "type": "bluesky.protocols.Readable"
+ },
+ "title": "Detectors",
+ "type": "array",
+ "uniqueItems": true
+ },
+ "spec": {
+ "$ref": "#/$defs/Spec"
+ },
+ "metadata": {
+ "title": "Metadata",
+ "type": "object"
+ }
+ },
+ "required": ["detectors", "spec"],
+ "title": "spec_scan",
+ "type": "object"
+ }
+ }
+ ]
+}
diff --git a/apps/visr/src/mocks/workflows-response.json b/apps/visr/src/mocks/workflows-response.json
new file mode 100644
index 0000000..424c19d
--- /dev/null
+++ b/apps/visr/src/mocks/workflows-response.json
@@ -0,0 +1,22 @@
+{
+ "data": {
+ "workflowTemplate": {
+ "name": "visr",
+ "maintainer": "visr-group",
+ "title": "visr",
+ "description": "Data processing for the Visible Light Spectroscopy beamline (ViSR).\n",
+ "arguments": {
+ "properties": {
+ "inpath": {
+ "default": "",
+ "type": "string"
+ }
+ },
+ "required": ["inpath"],
+ "type": "object"
+ },
+ "uiSchema": null,
+ "repository": null
+ }
+ }
+}
diff --git a/apps/visr/src/routes/Dashboard.tsx b/apps/visr/src/routes/Dashboard.tsx
new file mode 100644
index 0000000..fa1f7c4
--- /dev/null
+++ b/apps/visr/src/routes/Dashboard.tsx
@@ -0,0 +1,53 @@
+import { Container, Typography, Button, Stack } from "@mui/material";
+import ArticleIcon from "@mui/icons-material/Article";
+import FeedIcon from "@mui/icons-material/Feed";
+import AddchartIcon from "@mui/icons-material/Addchart";
+import { Link } from "react-router-dom";
+import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView";
+import GetInstrumentSessions from "../components/InstrumentSessionSelection/InstrumentSession";
+
+function Dashboard() {
+ return (
+ <>
+
+
+
+ Welcome to ViSR
+
+
+ }
+ sx={{ width: 150, height: 50 }}
+ >
+ Spectroscopy
+
+ }
+ sx={{ width: 150, height: 50 }}
+ >
+ Plans
+
+ }
+ sx={{ width: 150, height: 50, gap: "0.5rem" }}
+ >
+ Workflows
+
+
+
+
+
+ >
+ );
+}
+
+export default Dashboard;
diff --git a/apps/visr/src/routes/Layout.tsx b/apps/visr/src/routes/Layout.tsx
new file mode 100644
index 0000000..09935f0
--- /dev/null
+++ b/apps/visr/src/routes/Layout.tsx
@@ -0,0 +1,15 @@
+import { Link, Outlet, useLocation } from "react-router-dom";
+import VisrNavbar from "../components/VisrNavbar";
+import { Breadcrumbs } from "@diamondlightsource/sci-react-ui";
+
+/* A common layout for all routes, consisting of Navbar and breadcrumbs */
+export function Layout() {
+ const location = useLocation();
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/visr/src/routes/Plans.tsx b/apps/visr/src/routes/Plans.tsx
new file mode 100644
index 0000000..80281d5
--- /dev/null
+++ b/apps/visr/src/routes/Plans.tsx
@@ -0,0 +1,31 @@
+import { useEffect, useState } from "react";
+import { Box } from "@mui/material";
+import { getPlans, type PlansResponse } from "../utils/api";
+import PlanBrowser from "../components/PlanBrowser/PlanBrowser";
+import PlanParameters from "../components/PlanBrowser/PlanParameters";
+
+function JsonFormsPlans() {
+ const [planData, setPlanData] = useState({ plans: [] });
+
+ useEffect(() => {
+ async function fetchPlans() {
+ const results = await getPlans();
+ setPlanData(results);
+ }
+
+ fetchPlans();
+ }, []);
+
+ return (
+ <>
+
+ }
+ />
+
+ >
+ );
+}
+
+export default JsonFormsPlans;
diff --git a/apps/visr/src/routes/Spectroscopy.tsx b/apps/visr/src/routes/Spectroscopy.tsx
new file mode 100644
index 0000000..ffa28fa
--- /dev/null
+++ b/apps/visr/src/routes/Spectroscopy.tsx
@@ -0,0 +1,14 @@
+import { Box } from "@mui/material";
+import SpectroscopyForm from "../components/SpectroscopyForm";
+
+function Spectroscopy() {
+ return (
+ <>
+
+
+
+ >
+ );
+}
+
+export default Spectroscopy;
diff --git a/apps/visr/src/routes/Workflows.tsx b/apps/visr/src/routes/Workflows.tsx
new file mode 100644
index 0000000..0501317
--- /dev/null
+++ b/apps/visr/src/routes/Workflows.tsx
@@ -0,0 +1,25 @@
+import { Suspense } from "react";
+import { Container, Box } from "@mui/material";
+import TemplateView from "../components/TemplateView";
+
+const Workflows: React.FC = () => {
+ return (
+ <>
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Workflows;
diff --git a/apps/visr/src/test/global.d.ts b/apps/visr/src/test/global.d.ts
new file mode 100644
index 0000000..edfa8b5
--- /dev/null
+++ b/apps/visr/src/test/global.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/apps/visr/src/utils/api.ts b/apps/visr/src/utils/api.ts
new file mode 100644
index 0000000..734c617
--- /dev/null
+++ b/apps/visr/src/utils/api.ts
@@ -0,0 +1,79 @@
+export interface Plan {
+ name: string;
+ description: string | undefined;
+ schema: object;
+}
+
+export interface PlansResponse {
+ plans: Plan[];
+}
+
+export interface TaskResponse {
+ task_id: string;
+}
+
+export interface TaskRequest {
+ name: string;
+ params: object;
+ instrument_session: string;
+}
+
+export async function getPlans(): Promise {
+ const url = "/api/plans";
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+ headers.append("X-Requested-By", "XMLHttpRequest");
+
+ const response = await fetch(url, {
+ method: "GET",
+ headers: headers,
+ });
+
+ return await response.json();
+}
+
+export async function createAndStartTask(
+ request: TaskRequest,
+): Promise {
+ const task = await createTask(request);
+ return await startTask(task.task_id);
+}
+
+export async function createTask(request: TaskRequest): Promise {
+ const url = "/api/tasks";
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+ headers.append("X-Requested-By", "XMLHttpRequest");
+
+ const response = await fetch(url, {
+ method: "POST",
+ headers: headers,
+ body: JSON.stringify(request),
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text();
+ throw new Error(
+ `Error ${response.status}: ${response.statusText}\n${errorText}`,
+ );
+ }
+ return await response.json();
+}
+
+export async function startTask(task_id: string): Promise {
+ const url = "/api/worker/task";
+
+ const headers = new Headers();
+ headers.append("Content-Type", "application/json");
+ headers.append("X-Requested-By", "XMLHttpRequest");
+
+ const response = await fetch(url, {
+ method: "PUT",
+ headers: headers,
+ body: JSON.stringify({ task_id: task_id }),
+ });
+
+ return await response.json();
+}
diff --git a/apps/visr/src/utils/common.ts b/apps/visr/src/utils/common.ts
new file mode 100644
index 0000000..e27649f
--- /dev/null
+++ b/apps/visr/src/utils/common.ts
@@ -0,0 +1,14 @@
+import { type Visit, regexToVisit } from "@diamondlightsource/sci-react-ui";
+
+const visitRegex = /^([a-z]{2})([1-9]\d*)-([1-9]\d*)/;
+export const visitWithTemplateRegex = new RegExp(`${visitRegex.source}-(.+)$`);
+
+export function visitTextToVisit(visitid?: string): Visit | null {
+ if (visitid) {
+ const parsedVisit = visitRegex.exec(visitid);
+ if (parsedVisit != null) {
+ return regexToVisit(parsedVisit);
+ }
+ }
+ return null;
+}
diff --git a/apps/visr/src/utils/schema.ts b/apps/visr/src/utils/schema.ts
new file mode 100644
index 0000000..d23d0d7
--- /dev/null
+++ b/apps/visr/src/utils/schema.ts
@@ -0,0 +1,25 @@
+const shouldJustBeStrings = [
+ "bluesky.protocols.Readable",
+ "bluesky.protocols.Movable",
+];
+
+const sanitizeSchema = (schema: object): object => {
+ const entries = Object.entries(schema);
+
+ const processed = entries.map(([key, value]) => {
+ if (key == "type" && shouldJustBeStrings.includes(value)) {
+ return [key, "string"];
+ } else if (
+ typeof value === "object" &&
+ !Array.isArray(value) &&
+ value !== null
+ ) {
+ return [key, sanitizeSchema(value)];
+ } else {
+ return [key, value];
+ }
+ });
+ return Object.fromEntries(processed);
+};
+
+export default sanitizeSchema;
diff --git a/apps/visr/src/utils/types.ts b/apps/visr/src/utils/types.ts
new file mode 100644
index 0000000..9599e83
--- /dev/null
+++ b/apps/visr/src/utils/types.ts
@@ -0,0 +1,94 @@
+import type { PayloadError } from "relay-runtime";
+
+export type TaskStatus =
+ | "PENDING"
+ | "RUNNING"
+ | "SUCCEEDED"
+ | "SKIPPED"
+ | "FAILED"
+ | "ERROR"
+ | "OMITTED";
+
+export interface Task {
+ id: string;
+ name: string;
+ status: TaskStatus;
+ depends?: string[];
+ artifacts: Artifact[];
+ workflow: string;
+ instrumentSession: Visit;
+ stepType: string;
+}
+
+export interface Artifact {
+ name: string;
+ url: string;
+ mimeType: string;
+ parentTask: string;
+}
+
+export interface TaskNode extends Task {
+ children?: TaskNode[];
+}
+
+export type WorkflowStatus =
+ | "Unknown"
+ | "WorkflowPendingStatus"
+ | "WorkflowRunningStatus"
+ | "WorkflowSucceededStatus"
+ | "WorkflowFailedStatus"
+ | "WorkflowErroredStatus";
+
+export interface Workflow {
+ name: string;
+ instrumentSession: Visit;
+ status: WorkflowStatus;
+}
+
+export interface Visit {
+ proposalCode: string;
+ proposalNumber: number;
+ number: number;
+}
+
+export interface Template {
+ name: string;
+ description?: string | null;
+ title?: string | null;
+ maintainer: string;
+ repository?: string | null;
+}
+
+export interface WorkflowStatusBool {
+ pending?: boolean;
+ running?: boolean;
+ succeeded?: boolean;
+ failed?: boolean;
+ error?: boolean;
+}
+
+export interface SubmissionSuccessMessage {
+ type: "success";
+ message: string;
+}
+
+export interface SubmissionNetworkErrorMessage {
+ type: "networkError";
+ error: Error;
+}
+
+export interface SubmissionGraphQLErrorMessage {
+ type: "graphQLError";
+ errors: PayloadError[];
+}
+
+export type JSONValue =
+ | string
+ | number
+ | boolean
+ | null
+ | JSONObject
+ | JSONValue[];
+export interface JSONObject {
+ [key: string]: JSONValue;
+}
diff --git a/apps/visr/src/vite-env.d.ts b/apps/visr/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/visr/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/visr/supergraph.graphql b/apps/visr/supergraph.graphql
new file mode 100644
index 0000000..eb96ae8
--- /dev/null
+++ b/apps/visr/supergraph.graphql
@@ -0,0 +1,648 @@
+type Artifact {
+ """
+ The file name of the artifact
+ """
+ name: String!
+ """
+ The download URL for the artifact
+ """
+ url: Url!
+ """
+ The MIME type of the artifact data
+ """
+ mimeType: String!
+}
+
+scalar Creator
+
+"""
+Implement the DateTime scalar
+
+The input/output is a string in RFC3339 format.
+"""
+scalar DateTime
+
+"""
+A scalar that can represent any JSON value.
+"""
+scalar JSON
+
+"""
+A scalar that can represent any JSON Object value.
+"""
+scalar JSONObject
+
+"""
+A single log line streamed from a pod
+"""
+type LogEntry {
+ """
+ The log line content
+ """
+ content: String!
+ """
+ The name of the pod producing the log
+ """
+ podName: String!
+}
+
+"""
+The root mutation of the service
+"""
+type Mutation {
+ submitWorkflowTemplate(
+ name: String!
+ visit: VisitInput!
+ parameters: JSON!
+ ): Workflow!
+}
+
+"""
+Information about pagination in a connection
+"""
+type PageInfo @shareable {
+ """
+ When paginating backwards, are there more items?
+ """
+ hasPreviousPage: Boolean!
+ """
+ When paginating forwards, are there more items?
+ """
+ hasNextPage: Boolean!
+ """
+ When paginating backwards, the cursor to continue.
+ """
+ startCursor: String
+ """
+ When paginating forwards, the cursor to continue.
+ """
+ endCursor: String
+}
+
+"""
+Supported DLS science groups
+"""
+enum ScienceGroup {
+ """
+ Macromolecular Crystallography
+ """
+ MX
+ """
+ Workflows Examples
+ """
+ EXAMPLES
+ """
+ Magnetic Materials
+ """
+ MAGNETIC_MATERIALS
+ """
+ Soft Condensed Matter
+ """
+ CONDENSED_MATTER
+ """
+ Imaging and Microscopy
+ """
+ IMAGING
+ """
+ Biological Cryo-Imaging
+ """
+ BIO_CRYO_IMAGING
+ """
+ Structures and Surfaces
+ """
+ SURFACES
+ """
+ Crystallography
+ """
+ CRYSTALLOGRAPHY
+ """
+ Spectroscopy
+ """
+ SPECTROSCOPY
+}
+
+"""
+The root mutation of the service
+"""
+type Subscription {
+ """
+ Processing to subscribe to logs for a single pod of a workflow
+ """
+ logs(visit: VisitInput!, workflowName: String!, taskId: String!): LogEntry!
+ """
+ Processing to subscribe to data for all workflows in a session
+ """
+ workflow(visit: VisitInput!, name: String!): Workflow!
+}
+
+type Task {
+ """
+ Unique name of the task
+ """
+ id: String!
+ """
+ Display name of the task
+ """
+ name: String!
+ """
+ Current status of a task
+ """
+ status: TaskStatus!
+ """
+ Parent of a task
+ """
+ depends: [String!]!
+ """
+ Children of a task
+ """
+ dependencies: [String!]!
+ """
+ Artifacts produced by a task
+ """
+ artifacts: [Artifact!]!
+ """
+ Node type - Pod, DAG, etc
+ """
+ stepType: String!
+ """
+ Start time for a task on a workflow
+ """
+ startTime: DateTime
+ """
+ End time for a task on a workflow
+ """
+ endTime: DateTime
+ """
+ A human readable message indicating details about why this step is in this condition
+ """
+ message: String
+}
+
+enum TaskStatus {
+ PENDING
+ RUNNING
+ SUCCEEDED
+ SKIPPED
+ FAILED
+ ERROR
+ OMITTED
+}
+
+scalar Template
+
+"""
+URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/)
+"""
+scalar Url
+
+"""
+A visit to an instrument as part of a session
+"""
+type Visit {
+ """
+ Project Proposal Code
+ """
+ proposalCode: String!
+ """
+ Project Proposal Number
+ """
+ proposalNumber: Int!
+ """
+ Session visit Number
+ """
+ number: Int!
+}
+
+"""
+A visit to an instrument as part of a session
+"""
+input VisitInput {
+ """
+ Project Proposal Code
+ """
+ proposalCode: String!
+ """
+ Project Proposal Number
+ """
+ proposalNumber: Int!
+ """
+ Session visit Number
+ """
+ number: Int!
+}
+
+type Workflow {
+ """
+ The name given to the workflow, unique within a given visit
+ """
+ name: String!
+ """
+ The visit the Workflow was run against
+ """
+ visit: Visit!
+ """
+ The current status of the workflow
+ """
+ status: WorkflowStatus
+ """
+ The top-level workflow parameters
+ """
+ parameters: JSONObject
+ """
+ The name of the template used to run the workflow
+ """
+ templateRef: String
+ """
+ The workflow creator
+ """
+ creator: WorkflowCreator!
+}
+
+type WorkflowConnection @shareable {
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+ """
+ A list of edges.
+ """
+ edges: [WorkflowEdge!]!
+ """
+ A list of nodes.
+ """
+ nodes: [Workflow!]!
+}
+
+"""
+Information about the creator of a workflow.
+"""
+type WorkflowCreator {
+ """
+ An identifier unique to the creator of the workflow.
+ Typically this is the creator's Fed-ID.
+ """
+ creatorId: String!
+}
+
+"""
+An edge in a connection.
+"""
+type WorkflowEdge @shareable {
+ """
+ The item at the end of the edge
+ """
+ node: Workflow!
+ """
+ A cursor for use in pagination
+ """
+ cursor: String!
+}
+
+"""
+All tasks in the workflow have errored
+"""
+type WorkflowErroredStatus {
+ """
+ Time at which this workflow started
+ """
+ startTime: DateTime!
+ """
+ Time at which this workflow completed
+ """
+ endTime: DateTime!
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+ """
+ Tasks created by the workflow
+ """
+ tasks: [Task!]!
+}
+
+"""
+All tasks in the workflow have failed
+"""
+type WorkflowFailedStatus {
+ """
+ Time at which this workflow started
+ """
+ startTime: DateTime!
+ """
+ Time at which this workflow completed
+ """
+ endTime: DateTime!
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+ """
+ Tasks created by the workflow
+ """
+ tasks: [Task!]!
+}
+
+"""
+All the supported Workflows filters
+"""
+input WorkflowFilter {
+ """
+ The status field for a workflow
+ """
+ workflowStatusFilter: WorkflowStatusFilter
+ """
+ The fedid of the user who created the workflow
+ """
+ creator: Creator
+ """
+ The name of the workflow template
+ """
+ template: Template
+}
+
+type WorkflowPendingStatus {
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+}
+
+type WorkflowRunningStatus {
+ """
+ Time at which this workflow started
+ """
+ startTime: DateTime!
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+ """
+ Tasks created by the workflow
+ """
+ tasks: [Task!]!
+}
+
+"""
+The status of a workflow
+"""
+union WorkflowStatus =
+ | WorkflowPendingStatus
+ | WorkflowRunningStatus
+ | WorkflowSucceededStatus
+ | WorkflowFailedStatus
+ | WorkflowErroredStatus
+
+"""
+Represents workflow status filters
+"""
+input WorkflowStatusFilter {
+ pending: Boolean! = false
+ running: Boolean! = false
+ succeeded: Boolean! = false
+ failed: Boolean! = false
+ error: Boolean! = false
+}
+
+"""
+All tasks in the workflow have succeded
+"""
+type WorkflowSucceededStatus {
+ """
+ Time at which this workflow started
+ """
+ startTime: DateTime!
+ """
+ Time at which this workflow completed
+ """
+ endTime: DateTime!
+ """
+ A human readable message indicating details about why the workflow is in this condition
+ """
+ message: String
+ """
+ Tasks created by the workflow
+ """
+ tasks: [Task!]!
+}
+
+type WorkflowTemplate {
+ """
+ The name given to the workflow template, globally unique
+ """
+ name: String!
+ """
+ The group who maintains the workflow template
+ """
+ maintainer: String!
+ """
+ A human readable title for the workflow template
+ """
+ title: String
+ """
+ A human readable description of the workflow which is created
+ """
+ description: String
+ """
+ The repository storing the code associated with this template.
+ """
+ repository: String
+ """
+ A JSON Schema describing the arguments of a Workflow Template
+ """
+ arguments: JSON!
+ """
+ A JSON Forms UI Schema describing how to render the arguments of the Workflow Template
+ """
+ uiSchema: JSON
+}
+
+type WorkflowTemplateConnection @shareable {
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+ """
+ A list of edges.
+ """
+ edges: [WorkflowTemplateEdge!]!
+ """
+ A list of nodes.
+ """
+ nodes: [WorkflowTemplate!]!
+}
+
+"""
+An edge in a connection.
+"""
+type WorkflowTemplateEdge @shareable {
+ """
+ The item at the end of the edge
+ """
+ node: WorkflowTemplate!
+ """
+ A cursor for use in pagination
+ """
+ cursor: String!
+}
+
+"""
+Supported label filters for ClusterWorkflowTemplates
+"""
+input WorkflowTemplatesFilter {
+ """
+ The science group owning the template eg imaging
+ """
+ scienceGroup: [ScienceGroup!]
+}
+
+"""
+Directs the executor to include this field or fragment only when the `if` argument is true.
+"""
+directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"""
+Directs the executor to skip this field or fragment when the `if` argument is true.
+"""
+directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"""
+Provides a scalar specification URL for specifying the behavior of custom scalar types.
+"""
+directive @specifiedBy(url: String!) on SCALAR
+
+type Account {
+ accountId: Int!
+ username: String!
+ emailAddress: String
+ title: String
+ givenName: String
+ familyName: String
+ type: AccountType!
+ state: AccountState!
+ proposalRoles: [ProposalAccount!]!
+ instrumentSessionRoles: [InstrumentSessionRole!]!
+}
+
+enum AccountState {
+ enabled
+ disabled
+}
+
+enum AccountType {
+ user
+ staff
+ functional
+}
+
+"""
+Date with time (isoformat)
+"""
+scalar DateTime
+
+type Instrument {
+ name: String!
+ scienceGroup: String
+ description: String
+ proposals: [Proposal!]!
+ instrumentSessions: [InstrumentSession!]!
+}
+
+type InstrumentSession {
+ instrumentSessionId: Int!
+ instrumentSessionNumber: Int!
+ startTime: DateTime
+ endTime: DateTime
+ type: String
+ state: String
+ riskRating: String
+ proposal: Proposal
+ instrument: Instrument!
+ roles: [InstrumentSessionRole!]!
+}
+
+type InstrumentSessionRole {
+ instrumentSession: InstrumentSession!
+ account: Account!
+ role: String!
+ onSite: Boolean!
+}
+
+type Proposal {
+ proposalNumber: Int!
+ proposalCategory: String
+ title: String
+ summary: String
+ state: ProposalState!
+ instrumentSessions: [InstrumentSession!]!
+ instruments: [Instrument!]!
+ roles: [ProposalAccount!]!
+}
+
+type ProposalAccount {
+ proposal: Proposal!
+ account: Account!
+ role: String!
+}
+
+enum ProposalState {
+ Open
+ Closed
+ Cancelled
+}
+
+type Query {
+ """
+ Get a single [`Workflow`] by proposal, visit, and name
+ """
+ workflow(visit: VisitInput!, name: String!): Workflow!
+ workflows(
+ visit: VisitInput!
+ cursor: String
+ limit: Int
+ filter: WorkflowFilter
+ ): WorkflowConnection!
+ workflowTemplate(name: String!): WorkflowTemplate!
+ workflowTemplates(
+ cursor: String
+ limit: Int
+ filter: WorkflowTemplatesFilter
+ ): WorkflowTemplateConnection!
+ """
+ Get a proposal by its number
+ """
+ proposal(proposalNumber: Int!): Proposal
+
+ """
+ Get a list of proposals
+ """
+ proposals(proposalCategory: String = null): [Proposal!]!
+
+ """
+ Get a instrument session
+ """
+ instrumentSession(
+ proposalNumber: Int!
+ instrumentSessionNumber: Int!
+ ): InstrumentSession
+
+ """
+ Get a instrument session
+ """
+ instrumentSessions(
+ proposalNumber: Int = null
+ proposalCategory: String = null
+ ): [InstrumentSession!]
+
+ """
+ Get an instrument
+ """
+ instrument(instrumentName: String!): Instrument
+
+ """
+ Get a list of instruments
+ """
+ instruments(scienceGroup: String = null): [Instrument!]!
+
+ """
+ Get an account
+ """
+ account(username: String!): Account
+}
diff --git a/apps/visr/tsconfig.json b/apps/visr/tsconfig.json
new file mode 100644
index 0000000..8657541
--- /dev/null
+++ b/apps/visr/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["vite/client", "@atlas/vitest-conf/global-types"]
+ },
+ "include": ["src"]
+}
diff --git a/apps/visr/vite.config.ts b/apps/visr/vite.config.ts
new file mode 100644
index 0000000..fab2919
--- /dev/null
+++ b/apps/visr/vite.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react-swc";
+import relay from "vite-plugin-relay";
+
+export default defineConfig({
+ plugins: [react(), relay],
+ define: {
+ global: {},
+ },
+});
diff --git a/apps/visr/vitest.config.ts b/apps/visr/vitest.config.ts
new file mode 100644
index 0000000..6dbadc2
--- /dev/null
+++ b/apps/visr/vitest.config.ts
@@ -0,0 +1,6 @@
+import { defineConfig } from "vitest/config";
+import baseConfig from "@atlas/vitest-conf/vitest.config";
+
+export default defineConfig({
+ ...baseConfig,
+});
diff --git a/docker/Dockerfile.web-app b/docker/Dockerfile.web-app
new file mode 100644
index 0000000..5719602
--- /dev/null
+++ b/docker/Dockerfile.web-app
@@ -0,0 +1,44 @@
+FROM node:22-alpine AS base
+
+# Use a consistent working directory across all stages
+WORKDIR /app
+
+# 1) Install dependencies
+FROM base AS deps
+
+ARG APP_NAME
+
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc* ./
+COPY packages ./packages
+COPY apps/${APP_NAME}/package.json ./apps/${APP_NAME}/
+
+# Install dependencies
+RUN corepack enable pnpm && pnpm i --frozen-lockfile --filter=@atlas/${APP_NAME}...
+
+# 2) Run the build
+FROM base AS builder
+
+ARG APP_NAME
+
+# Copy all node_modules from deps stage
+COPY --from=deps /app ./
+
+# Copy source files
+COPY apps/${APP_NAME} ./apps/${APP_NAME}
+COPY packages ./packages
+COPY turbo.json package.json pnpm-workspace.yaml tsconfig.json ./
+RUN corepack enable pnpm && pnpm --filter=@atlas/${APP_NAME}... build
+
+# 3) Create minimal image to serve the app
+FROM nginxinc/nginx-unprivileged:1.25-alpine AS runner
+
+ARG APP_NAME
+
+# Copy built files to nginx web root
+COPY --from=builder /app/apps/${APP_NAME}/dist /usr/share/nginx/html
+
+# Copy your custom nginx config
+COPY docker/nginx.conf /etc/nginx/nginx.conf
+
+EXPOSE 8080
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docker/nginx.conf b/docker/nginx.conf
new file mode 100644
index 0000000..6eaa276
--- /dev/null
+++ b/docker/nginx.conf
@@ -0,0 +1,61 @@
+worker_processes auto; # Let Nginx decide based on CPU cores
+pid /tmp/nginx.pid;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+
+ # Logging to stdout/stderr is standard for Docker
+ access_log /dev/stdout;
+ error_log /dev/stderr warn;
+
+ sendfile on;
+ tcp_nopush on;
+ tcp_nodelay on;
+ keepalive_timeout 65;
+ types_hash_max_size 2048;
+
+ # Gzip Compression - Critical for web app performance
+ gzip on;
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 6;
+ gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml image/svg+xml;
+
+ # Temp paths for unprivileged user
+ client_body_temp_path /tmp/client_temp;
+ proxy_temp_path /tmp/proxy_temp_path;
+ fastcgi_temp_path /tmp/fastcgi_temp;
+ uwsgi_temp_path /tmp/uwsgi_temp;
+ scgi_temp_path /tmp/scgi_temp;
+
+ server {
+ listen 8080;
+ server_name _;
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # SPA routing: try file, then directory, then fall back to index.html
+ location / {
+ try_files $uri $uri/ /index.html;
+ add_header Cache-Control "no-store" always;
+ }
+
+ # Cache hashed assets (JS/CSS/Images) for a long time
+ # This assumes your build tool uses hashes in filenames
+ location ~* \.(?:js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ access_log off;
+ }
+
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 66c1e7e..c4863b6 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
"sort-package-json": "^3.4.0",
"turbo": "^2.5.6",
"typescript": "^5.9.2",
- "typescript-eslint": "^8.44.1"
+ "typescript-eslint": "^8.44.1",
+ "vitest": "^4.0.13"
},
"packageManager": "pnpm@10.17.1"
}
diff --git a/packages/vitest-conf/README.md b/packages/vitest-conf/README.md
index a110631..faba088 100644
--- a/packages/vitest-conf/README.md
+++ b/packages/vitest-conf/README.md
@@ -8,41 +8,45 @@ This package centralises all testing setup — including Vitest configuration, j
## Features
- - Shared **Vitest** configuration via `vitest.config.ts`
- - Preconfigured **jsdom** environment for React component testing
- - Includes **@testing-library/jest-dom** matchers globally
- - TypeScript definitions for `expect`, `describe`, etc.
- - Central place to update dependencies like `vitest` or `@testing-library/*`
+- Shared **Vitest** configuration via `vitest.config.ts`
+- Preconfigured **jsdom** environment for React component testing
+- Includes **@testing-library/jest-dom** matchers globally
+- TypeScript definitions for `expect`, `describe`, etc.
+- Central place to update dependencies like `@testing-library/*`
- ---
+---
+
+## Usage
- ## Usage
+1. add `vitest` and `@atlas/vitest-conf` as dev dependencies in `apps/my-app/package.json`:
- 1. add `@atlas/vitest-conf` as a workspace dependency in `apps/my-app/package.json`:
```json
"devDependencies": {
+ "vitest": "*",
"@atlas/vitest-conf": "workspace:*"
}
```
- 2. Create a `vitest.config.ts` that simply reuses the shared base config:
+2. Create a `vitest.config.ts` that simply reuses the shared base config:
+
```ts
// apps/my-app/vitest.config.ts
import { defineConfig } from "vitest/config";
-import baseConfig from "@atlas/vitest-conf";
+import baseConfig from "@atlas/vitest-conf/vitest.config";
export default defineConfig({
...baseConfig,
});
```
- 3. Include global type definitions in `apps/my-app/tsconfig.json`:
+3. Include global type definitions in `apps/my-app/tsconfig.json`:
+
```json
{
- "compilerOptions": {
- "types": ["@atlas/vitest-conf/global-types"]
- }
+ "compilerOptions": {
+ "types": ["@atlas/vitest-conf/global-types"]
+ }
}
```
-4. Write some tests! Vitest will find tests that match `**/*.test.{ts,tsx}`.
\ No newline at end of file
+4. Write some tests! Vitest will find tests that match `**/*.test.{ts,tsx}`.
diff --git a/packages/vitest-conf/package.json b/packages/vitest-conf/package.json
index 814ad29..a80d95b 100644
--- a/packages/vitest-conf/package.json
+++ b/packages/vitest-conf/package.json
@@ -5,6 +5,10 @@
"type": "module",
"exports": {
".": {
+ "import": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "./vitest.config": {
"import": "./dist/vitest.config.js",
"types": "./dist/vitest.config.d.ts"
},
@@ -16,8 +20,8 @@
"types": "./types/global-types/index.d.ts"
}
},
- "main": "./dist/vitest.config.js",
- "types": "./dist/vitest.config.d.ts",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
"files": [
"dist",
"types"
@@ -30,7 +34,9 @@
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
- "jsdom": "^26.1.0",
- "vitest": "^3.2.4"
+ "jsdom": "^26.1.0"
+ },
+ "peerDependencies": {
+ "vitest": "*"
}
}
diff --git a/packages/vitest-conf/src/index.ts b/packages/vitest-conf/src/index.ts
new file mode 100644
index 0000000..33195b3
--- /dev/null
+++ b/packages/vitest-conf/src/index.ts
@@ -0,0 +1,3 @@
+export * from "@testing-library/react";
+export * from "@testing-library/user-event";
+export * from "@testing-library/dom";
diff --git a/packages/vitest-conf/tsconfig.json b/packages/vitest-conf/tsconfig.json
index 34849b5..72d7c4c 100644
--- a/packages/vitest-conf/tsconfig.json
+++ b/packages/vitest-conf/tsconfig.json
@@ -1,12 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
+ "allowImportingTsExtensions": false,
"declaration": true,
- "declarationMap": false,
- "emitDeclarationOnly": false,
"esModuleInterop": true,
- "resolveJsonModule": true,
+ "noEmit": false,
+ "outDir": "dist",
"rootDir": "src"
},
- "include": ["src", "src/globals.d.ts"]
+ "exclude": ["dist", "node_modules"],
+ "include": ["src"]
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
deleted file mode 100644
index 94212c2..0000000
--- a/pnpm-lock.yaml
+++ /dev/null
@@ -1,3102 +0,0 @@
-lockfileVersion: '9.0'
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
-importers:
-
- .:
- dependencies:
- react:
- specifier: ^18.3.1
- version: 18.3.1
- react-dom:
- specifier: ^18.3.1
- version: 18.3.1(react@18.3.1)
- devDependencies:
- '@eslint/js':
- specifier: ^9.36.0
- version: 9.36.0
- '@types/react':
- specifier: ^18.3.1
- version: 18.3.24
- '@types/react-dom':
- specifier: ^18.3.1
- version: 18.3.7(@types/react@18.3.24)
- eslint:
- specifier: ^9.36.0
- version: 9.36.0
- prettier:
- specifier: ^3.6.2
- version: 3.6.2
- sort-package-json:
- specifier: ^3.4.0
- version: 3.4.0
- turbo:
- specifier: ^2.5.6
- version: 2.5.6
- typescript:
- specifier: ^5.9.2
- version: 5.9.2
- typescript-eslint:
- specifier: ^8.44.1
- version: 8.44.1(eslint@9.36.0)(typescript@5.9.2)
-
- packages/vitest-conf:
- devDependencies:
- '@testing-library/dom':
- specifier: ^10.4.1
- version: 10.4.1
- '@testing-library/jest-dom':
- specifier: ^6.6.4
- version: 6.8.0
- '@testing-library/react':
- specifier: ^16.3.0
- version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@testing-library/user-event':
- specifier: ^14.6.1
- version: 14.6.1(@testing-library/dom@10.4.1)
- jsdom:
- specifier: ^26.1.0
- version: 26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
- vitest:
- specifier: ^3.2.4
- version: 3.2.4(@types/node@24.5.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@24.5.2)(typescript@5.9.2))
-
-packages:
-
- '@adobe/css-tools@4.4.4':
- resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
-
- '@asamuzakjp/css-color@3.2.0':
- resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
-
- '@babel/code-frame@7.27.1':
- resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
- engines: {node: '>=6.9.0'}
-
- '@babel/helper-validator-identifier@7.27.1':
- resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
- engines: {node: '>=6.9.0'}
-
- '@babel/runtime@7.28.4':
- resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
- engines: {node: '>=6.9.0'}
-
- '@bundled-es-modules/cookie@2.0.1':
- resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
-
- '@bundled-es-modules/statuses@1.0.1':
- resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
-
- '@csstools/color-helpers@5.1.0':
- resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
- engines: {node: '>=18'}
-
- '@csstools/css-calc@2.1.4':
- resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
- engines: {node: '>=18'}
- peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.5
- '@csstools/css-tokenizer': ^3.0.4
-
- '@csstools/css-color-parser@3.1.0':
- resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
- engines: {node: '>=18'}
- peerDependencies:
- '@csstools/css-parser-algorithms': ^3.0.5
- '@csstools/css-tokenizer': ^3.0.4
-
- '@csstools/css-parser-algorithms@3.0.5':
- resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
- engines: {node: '>=18'}
- peerDependencies:
- '@csstools/css-tokenizer': ^3.0.4
-
- '@csstools/css-tokenizer@3.0.4':
- resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
- engines: {node: '>=18'}
-
- '@esbuild/aix-ppc64@0.25.10':
- resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [aix]
-
- '@esbuild/android-arm64@0.25.10':
- resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [android]
-
- '@esbuild/android-arm@0.25.10':
- resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [android]
-
- '@esbuild/android-x64@0.25.10':
- resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [android]
-
- '@esbuild/darwin-arm64@0.25.10':
- resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [darwin]
-
- '@esbuild/darwin-x64@0.25.10':
- resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [darwin]
-
- '@esbuild/freebsd-arm64@0.25.10':
- resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [freebsd]
-
- '@esbuild/freebsd-x64@0.25.10':
- resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [freebsd]
-
- '@esbuild/linux-arm64@0.25.10':
- resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [linux]
-
- '@esbuild/linux-arm@0.25.10':
- resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
- engines: {node: '>=18'}
- cpu: [arm]
- os: [linux]
-
- '@esbuild/linux-ia32@0.25.10':
- resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [linux]
-
- '@esbuild/linux-loong64@0.25.10':
- resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
- engines: {node: '>=18'}
- cpu: [loong64]
- os: [linux]
-
- '@esbuild/linux-mips64el@0.25.10':
- resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
- engines: {node: '>=18'}
- cpu: [mips64el]
- os: [linux]
-
- '@esbuild/linux-ppc64@0.25.10':
- resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
- engines: {node: '>=18'}
- cpu: [ppc64]
- os: [linux]
-
- '@esbuild/linux-riscv64@0.25.10':
- resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
- engines: {node: '>=18'}
- cpu: [riscv64]
- os: [linux]
-
- '@esbuild/linux-s390x@0.25.10':
- resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
- engines: {node: '>=18'}
- cpu: [s390x]
- os: [linux]
-
- '@esbuild/linux-x64@0.25.10':
- resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [linux]
-
- '@esbuild/netbsd-arm64@0.25.10':
- resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [netbsd]
-
- '@esbuild/netbsd-x64@0.25.10':
- resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [netbsd]
-
- '@esbuild/openbsd-arm64@0.25.10':
- resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openbsd]
-
- '@esbuild/openbsd-x64@0.25.10':
- resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [openbsd]
-
- '@esbuild/openharmony-arm64@0.25.10':
- resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [openharmony]
-
- '@esbuild/sunos-x64@0.25.10':
- resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [sunos]
-
- '@esbuild/win32-arm64@0.25.10':
- resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
- engines: {node: '>=18'}
- cpu: [arm64]
- os: [win32]
-
- '@esbuild/win32-ia32@0.25.10':
- resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
- engines: {node: '>=18'}
- cpu: [ia32]
- os: [win32]
-
- '@esbuild/win32-x64@0.25.10':
- resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
- engines: {node: '>=18'}
- cpu: [x64]
- os: [win32]
-
- '@eslint-community/eslint-utils@4.9.0':
- resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- peerDependencies:
- eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
- '@eslint-community/regexpp@4.12.1':
- resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
- engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
-
- '@eslint/config-array@0.21.0':
- resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/config-helpers@0.3.1':
- resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/core@0.15.2':
- resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/eslintrc@3.3.1':
- resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/js@9.36.0':
- resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/object-schema@2.1.6':
- resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/plugin-kit@0.3.5':
- resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@humanfs/core@0.19.1':
- resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
- engines: {node: '>=18.18.0'}
-
- '@humanfs/node@0.16.7':
- resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
- engines: {node: '>=18.18.0'}
-
- '@humanwhocodes/module-importer@1.0.1':
- resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
- engines: {node: '>=12.22'}
-
- '@humanwhocodes/retry@0.4.3':
- resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
- engines: {node: '>=18.18'}
-
- '@inquirer/ansi@1.0.0':
- resolution: {integrity: sha512-JWaTfCxI1eTmJ1BIv86vUfjVatOdxwD0DAVKYevY8SazeUUZtW+tNbsdejVO1GYE0GXJW1N1ahmiC3TFd+7wZA==}
- engines: {node: '>=18'}
-
- '@inquirer/confirm@5.1.18':
- resolution: {integrity: sha512-MilmWOzHa3Ks11tzvuAmFoAd/wRuaP3SwlT1IZhyMke31FKLxPiuDWcGXhU+PKveNOpAc4axzAgrgxuIJJRmLw==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@inquirer/core@10.2.2':
- resolution: {integrity: sha512-yXq/4QUnk4sHMtmbd7irwiepjB8jXU0kkFRL4nr/aDBA2mDz13cMakEWdDwX3eSCTkk03kwcndD1zfRAIlELxA==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@inquirer/figures@1.0.13':
- resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==}
- engines: {node: '>=18'}
-
- '@inquirer/type@3.0.8':
- resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==}
- engines: {node: '>=18'}
- peerDependencies:
- '@types/node': '>=18'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
- '@jridgewell/sourcemap-codec@1.5.5':
- resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
-
- '@mswjs/interceptors@0.39.6':
- resolution: {integrity: sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==}
- engines: {node: '>=18'}
-
- '@nodelib/fs.scandir@2.1.5':
- resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
- engines: {node: '>= 8'}
-
- '@nodelib/fs.stat@2.0.5':
- resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
- engines: {node: '>= 8'}
-
- '@nodelib/fs.walk@1.2.8':
- resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
- engines: {node: '>= 8'}
-
- '@open-draft/deferred-promise@2.2.0':
- resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
-
- '@open-draft/logger@0.3.0':
- resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
-
- '@open-draft/until@2.1.0':
- resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
-
- '@rollup/rollup-android-arm-eabi@4.52.2':
- resolution: {integrity: sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==}
- cpu: [arm]
- os: [android]
-
- '@rollup/rollup-android-arm64@4.52.2':
- resolution: {integrity: sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==}
- cpu: [arm64]
- os: [android]
-
- '@rollup/rollup-darwin-arm64@4.52.2':
- resolution: {integrity: sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==}
- cpu: [arm64]
- os: [darwin]
-
- '@rollup/rollup-darwin-x64@4.52.2':
- resolution: {integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==}
- cpu: [x64]
- os: [darwin]
-
- '@rollup/rollup-freebsd-arm64@4.52.2':
- resolution: {integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==}
- cpu: [arm64]
- os: [freebsd]
-
- '@rollup/rollup-freebsd-x64@4.52.2':
- resolution: {integrity: sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==}
- cpu: [x64]
- os: [freebsd]
-
- '@rollup/rollup-linux-arm-gnueabihf@4.52.2':
- resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==}
- cpu: [arm]
- os: [linux]
-
- '@rollup/rollup-linux-arm-musleabihf@4.52.2':
- resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==}
- cpu: [arm]
- os: [linux]
-
- '@rollup/rollup-linux-arm64-gnu@4.52.2':
- resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==}
- cpu: [arm64]
- os: [linux]
-
- '@rollup/rollup-linux-arm64-musl@4.52.2':
- resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==}
- cpu: [arm64]
- os: [linux]
-
- '@rollup/rollup-linux-loong64-gnu@4.52.2':
- resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==}
- cpu: [loong64]
- os: [linux]
-
- '@rollup/rollup-linux-ppc64-gnu@4.52.2':
- resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==}
- cpu: [ppc64]
- os: [linux]
-
- '@rollup/rollup-linux-riscv64-gnu@4.52.2':
- resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==}
- cpu: [riscv64]
- os: [linux]
-
- '@rollup/rollup-linux-riscv64-musl@4.52.2':
- resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==}
- cpu: [riscv64]
- os: [linux]
-
- '@rollup/rollup-linux-s390x-gnu@4.52.2':
- resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==}
- cpu: [s390x]
- os: [linux]
-
- '@rollup/rollup-linux-x64-gnu@4.52.2':
- resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==}
- cpu: [x64]
- os: [linux]
-
- '@rollup/rollup-linux-x64-musl@4.52.2':
- resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==}
- cpu: [x64]
- os: [linux]
-
- '@rollup/rollup-openharmony-arm64@4.52.2':
- resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==}
- cpu: [arm64]
- os: [openharmony]
-
- '@rollup/rollup-win32-arm64-msvc@4.52.2':
- resolution: {integrity: sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==}
- cpu: [arm64]
- os: [win32]
-
- '@rollup/rollup-win32-ia32-msvc@4.52.2':
- resolution: {integrity: sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==}
- cpu: [ia32]
- os: [win32]
-
- '@rollup/rollup-win32-x64-gnu@4.52.2':
- resolution: {integrity: sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==}
- cpu: [x64]
- os: [win32]
-
- '@rollup/rollup-win32-x64-msvc@4.52.2':
- resolution: {integrity: sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==}
- cpu: [x64]
- os: [win32]
-
- '@testing-library/dom@10.4.1':
- resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
- engines: {node: '>=18'}
-
- '@testing-library/jest-dom@6.8.0':
- resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==}
- engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
-
- '@testing-library/react@16.3.0':
- resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==}
- 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
-
- '@testing-library/user-event@14.6.1':
- resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==}
- engines: {node: '>=12', npm: '>=6'}
- peerDependencies:
- '@testing-library/dom': '>=7.21.4'
-
- '@types/aria-query@5.0.4':
- resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
-
- '@types/chai@5.2.2':
- resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
-
- '@types/cookie@0.6.0':
- resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
-
- '@types/deep-eql@4.0.2':
- resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
-
- '@types/estree@1.0.8':
- resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
-
- '@types/json-schema@7.0.15':
- resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
-
- '@types/node@24.5.2':
- resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==}
-
- '@types/prop-types@15.7.15':
- resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
-
- '@types/react-dom@18.3.7':
- resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
- peerDependencies:
- '@types/react': ^18.0.0
-
- '@types/react@18.3.24':
- resolution: {integrity: sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==}
-
- '@types/statuses@2.0.6':
- resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
-
- '@typescript-eslint/eslint-plugin@8.44.1':
- resolution: {integrity: sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- '@typescript-eslint/parser': ^8.44.1
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/parser@8.44.1':
- resolution: {integrity: sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/project-service@8.44.1':
- resolution: {integrity: sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/scope-manager@8.44.1':
- resolution: {integrity: sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/tsconfig-utils@8.44.1':
- resolution: {integrity: sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/type-utils@8.44.1':
- resolution: {integrity: sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/types@8.44.1':
- resolution: {integrity: sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@typescript-eslint/typescript-estree@8.44.1':
- resolution: {integrity: sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/utils@8.44.1':
- resolution: {integrity: sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <6.0.0'
-
- '@typescript-eslint/visitor-keys@8.44.1':
- resolution: {integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@vitest/expect@3.2.4':
- resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
-
- '@vitest/mocker@3.2.4':
- resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
- peerDependencies:
- msw: ^2.4.9
- vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
- peerDependenciesMeta:
- msw:
- optional: true
- vite:
- optional: true
-
- '@vitest/pretty-format@3.2.4':
- resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
-
- '@vitest/runner@3.2.4':
- resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
-
- '@vitest/snapshot@3.2.4':
- resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
-
- '@vitest/spy@3.2.4':
- resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
-
- '@vitest/utils@3.2.4':
- resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
-
- acorn-jsx@5.3.2:
- resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
- peerDependencies:
- acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
-
- acorn@8.15.0:
- resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
- engines: {node: '>=0.4.0'}
- hasBin: true
-
- agent-base@7.1.4:
- resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
- engines: {node: '>= 14'}
-
- ajv@6.12.6:
- resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
-
- ansi-regex@5.0.1:
- resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
- engines: {node: '>=8'}
-
- ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
- engines: {node: '>=8'}
-
- ansi-styles@5.2.0:
- resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
- engines: {node: '>=10'}
-
- argparse@2.0.1:
- resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-
- aria-query@5.3.0:
- resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
-
- aria-query@5.3.2:
- resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
- engines: {node: '>= 0.4'}
-
- assertion-error@2.0.1:
- resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
- engines: {node: '>=12'}
-
- balanced-match@1.0.2:
- resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-
- brace-expansion@1.1.12:
- resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
-
- brace-expansion@2.0.2:
- resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
-
- braces@3.0.3:
- resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
- engines: {node: '>=8'}
-
- bufferutil@4.0.9:
- resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==}
- engines: {node: '>=6.14.2'}
-
- cac@6.7.14:
- resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
- engines: {node: '>=8'}
-
- callsites@3.1.0:
- resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
- engines: {node: '>=6'}
-
- chai@5.3.3:
- resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
- engines: {node: '>=18'}
-
- chalk@4.1.2:
- resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
- engines: {node: '>=10'}
-
- check-error@2.1.1:
- resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
- engines: {node: '>= 16'}
-
- cli-width@4.1.0:
- resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
- engines: {node: '>= 12'}
-
- cliui@8.0.1:
- resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
- engines: {node: '>=12'}
-
- color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
-
- color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
- concat-map@0.0.1:
- resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-
- cookie@0.7.2:
- resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
- engines: {node: '>= 0.6'}
-
- cross-spawn@7.0.6:
- resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
- engines: {node: '>= 8'}
-
- css.escape@1.5.1:
- resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
-
- cssstyle@4.6.0:
- resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
- engines: {node: '>=18'}
-
- csstype@3.1.3:
- resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
-
- data-urls@5.0.0:
- resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
- engines: {node: '>=18'}
-
- debug@4.4.3:
- resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
-
- decimal.js@10.6.0:
- resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
-
- deep-eql@5.0.2:
- resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
- engines: {node: '>=6'}
-
- deep-is@0.1.4:
- resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
-
- dequal@2.0.3:
- resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
- engines: {node: '>=6'}
-
- detect-indent@7.0.2:
- resolution: {integrity: sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==}
- engines: {node: '>=12.20'}
-
- detect-newline@4.0.1:
- resolution: {integrity: sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
- dom-accessibility-api@0.5.16:
- resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
-
- dom-accessibility-api@0.6.3:
- resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
-
- emoji-regex@8.0.0:
- resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
-
- entities@6.0.1:
- resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
- engines: {node: '>=0.12'}
-
- es-module-lexer@1.7.0:
- resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
-
- esbuild@0.25.10:
- resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
- engines: {node: '>=18'}
- hasBin: true
-
- escalade@3.2.0:
- resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
- engines: {node: '>=6'}
-
- escape-string-regexp@4.0.0:
- resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
- engines: {node: '>=10'}
-
- eslint-scope@8.4.0:
- resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- eslint-visitor-keys@3.4.3:
- resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
- eslint-visitor-keys@4.2.1:
- resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- eslint@9.36.0:
- resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- hasBin: true
- peerDependencies:
- jiti: '*'
- peerDependenciesMeta:
- jiti:
- optional: true
-
- espree@10.4.0:
- resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- esquery@1.6.0:
- resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
- engines: {node: '>=0.10'}
-
- esrecurse@4.3.0:
- resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
- engines: {node: '>=4.0'}
-
- estraverse@5.3.0:
- resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
- engines: {node: '>=4.0'}
-
- estree-walker@3.0.3:
- resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
-
- esutils@2.0.3:
- resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
- engines: {node: '>=0.10.0'}
-
- expect-type@1.2.2:
- resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
- engines: {node: '>=12.0.0'}
-
- fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
- fast-glob@3.3.3:
- resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
- engines: {node: '>=8.6.0'}
-
- fast-json-stable-stringify@2.1.0:
- resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
-
- fast-levenshtein@2.0.6:
- resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
-
- fastq@1.19.1:
- resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
-
- fdir@6.5.0:
- resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
- engines: {node: '>=12.0.0'}
- peerDependencies:
- picomatch: ^3 || ^4
- peerDependenciesMeta:
- picomatch:
- optional: true
-
- file-entry-cache@8.0.0:
- resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
- engines: {node: '>=16.0.0'}
-
- fill-range@7.1.1:
- resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
- engines: {node: '>=8'}
-
- find-up@5.0.0:
- resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
- engines: {node: '>=10'}
-
- flat-cache@4.0.1:
- resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
- engines: {node: '>=16'}
-
- flatted@3.3.3:
- resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
-
- fsevents@2.3.3:
- resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
- engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
- os: [darwin]
-
- get-caller-file@2.0.5:
- resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
- engines: {node: 6.* || 8.* || >= 10.*}
-
- git-hooks-list@4.1.1:
- resolution: {integrity: sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==}
-
- glob-parent@5.1.2:
- resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
- engines: {node: '>= 6'}
-
- glob-parent@6.0.2:
- resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
- engines: {node: '>=10.13.0'}
-
- globals@14.0.0:
- resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
- engines: {node: '>=18'}
-
- graphemer@1.4.0:
- resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
-
- graphql@16.11.0:
- resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==}
- engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
-
- has-flag@4.0.0:
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
- engines: {node: '>=8'}
-
- headers-polyfill@4.0.3:
- resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==}
-
- html-encoding-sniffer@4.0.0:
- resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
- engines: {node: '>=18'}
-
- http-proxy-agent@7.0.2:
- resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
- engines: {node: '>= 14'}
-
- https-proxy-agent@7.0.6:
- resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
- engines: {node: '>= 14'}
-
- iconv-lite@0.6.3:
- resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
- engines: {node: '>=0.10.0'}
-
- ignore@5.3.2:
- resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
- engines: {node: '>= 4'}
-
- ignore@7.0.5:
- resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
- engines: {node: '>= 4'}
-
- import-fresh@3.3.1:
- resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
- engines: {node: '>=6'}
-
- imurmurhash@0.1.4:
- resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
- engines: {node: '>=0.8.19'}
-
- indent-string@4.0.0:
- resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
- engines: {node: '>=8'}
-
- is-extglob@2.1.1:
- resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
- engines: {node: '>=0.10.0'}
-
- is-fullwidth-code-point@3.0.0:
- resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
- engines: {node: '>=8'}
-
- is-glob@4.0.3:
- resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
- engines: {node: '>=0.10.0'}
-
- is-node-process@1.2.0:
- resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
-
- is-number@7.0.0:
- resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
- engines: {node: '>=0.12.0'}
-
- is-plain-obj@4.1.0:
- resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
- engines: {node: '>=12'}
-
- is-potential-custom-element-name@1.0.1:
- resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
-
- isexe@2.0.0:
- resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-
- js-tokens@4.0.0:
- resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
-
- js-tokens@9.0.1:
- resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
-
- js-yaml@4.1.0:
- resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
- hasBin: true
-
- jsdom@26.1.0:
- resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
- engines: {node: '>=18'}
- peerDependencies:
- canvas: ^3.0.0
- peerDependenciesMeta:
- canvas:
- optional: true
-
- json-buffer@3.0.1:
- resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
-
- json-schema-traverse@0.4.1:
- resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
-
- json-stable-stringify-without-jsonify@1.0.1:
- resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
-
- keyv@4.5.4:
- resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
-
- levn@0.4.1:
- resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
- engines: {node: '>= 0.8.0'}
-
- locate-path@6.0.0:
- resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
- engines: {node: '>=10'}
-
- lodash.merge@4.6.2:
- resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-
- loose-envify@1.4.0:
- resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
- hasBin: true
-
- loupe@3.2.1:
- resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
-
- lru-cache@10.4.3:
- resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
-
- lz-string@1.5.0:
- resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
- hasBin: true
-
- magic-string@0.30.19:
- resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
-
- merge2@1.4.1:
- resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
- engines: {node: '>= 8'}
-
- micromatch@4.0.8:
- resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
- engines: {node: '>=8.6'}
-
- min-indent@1.0.1:
- resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
- engines: {node: '>=4'}
-
- minimatch@3.1.2:
- resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
-
- minimatch@9.0.5:
- resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
- engines: {node: '>=16 || 14 >=14.17'}
-
- ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
-
- msw@2.11.3:
- resolution: {integrity: sha512-878imp8jxIpfzuzxYfX0qqTq1IFQz/1/RBHs/PyirSjzi+xKM/RRfIpIqHSCWjH0GxidrjhgiiXC+DWXNDvT9w==}
- engines: {node: '>=18'}
- hasBin: true
- peerDependencies:
- typescript: '>= 4.8.x'
- peerDependenciesMeta:
- typescript:
- optional: true
-
- mute-stream@2.0.0:
- resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
- engines: {node: ^18.17.0 || >=20.5.0}
-
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
-
- natural-compare@1.4.0:
- resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
-
- node-gyp-build@4.8.4:
- resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
- hasBin: true
-
- nwsapi@2.2.22:
- resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
-
- optionator@0.9.4:
- resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
- engines: {node: '>= 0.8.0'}
-
- outvariant@1.4.3:
- resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
-
- p-limit@3.1.0:
- resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
- engines: {node: '>=10'}
-
- p-locate@5.0.0:
- resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
- engines: {node: '>=10'}
-
- parent-module@1.0.1:
- resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
- engines: {node: '>=6'}
-
- parse5@7.3.0:
- resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
-
- path-exists@4.0.0:
- resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
- engines: {node: '>=8'}
-
- path-key@3.1.1:
- resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
- engines: {node: '>=8'}
-
- path-to-regexp@6.3.0:
- resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
-
- pathe@2.0.3:
- resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
-
- pathval@2.0.1:
- resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
- engines: {node: '>= 14.16'}
-
- picocolors@1.1.1:
- resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
-
- picomatch@2.3.1:
- resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
- engines: {node: '>=8.6'}
-
- picomatch@4.0.3:
- resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
- engines: {node: '>=12'}
-
- postcss@8.5.6:
- resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
- engines: {node: ^10 || ^12 || >=14}
-
- prelude-ls@1.2.1:
- resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
- engines: {node: '>= 0.8.0'}
-
- prettier@3.6.2:
- resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
- engines: {node: '>=14'}
- hasBin: true
-
- pretty-format@27.5.1:
- resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
- engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
-
- punycode@2.3.1:
- resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
- engines: {node: '>=6'}
-
- queue-microtask@1.2.3:
- resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
-
- react-dom@18.3.1:
- resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
- peerDependencies:
- react: ^18.3.1
-
- react-is@17.0.2:
- resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
-
- react@18.3.1:
- resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
- engines: {node: '>=0.10.0'}
-
- redent@3.0.0:
- resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
- engines: {node: '>=8'}
-
- require-directory@2.1.1:
- resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
- engines: {node: '>=0.10.0'}
-
- resolve-from@4.0.0:
- resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
- engines: {node: '>=4'}
-
- rettime@0.7.0:
- resolution: {integrity: sha512-LPRKoHnLKd/r3dVxcwO7vhCW+orkOGj9ViueosEBK6ie89CijnfRlhaDhHq/3Hxu4CkWQtxwlBG0mzTQY6uQjw==}
-
- reusify@1.1.0:
- resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
- engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
-
- rollup@4.52.2:
- resolution: {integrity: sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==}
- engines: {node: '>=18.0.0', npm: '>=8.0.0'}
- hasBin: true
-
- rrweb-cssom@0.8.0:
- resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
-
- run-parallel@1.2.0:
- resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
-
- safer-buffer@2.1.2:
- resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
-
- saxes@6.0.0:
- resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
- engines: {node: '>=v12.22.7'}
-
- scheduler@0.23.2:
- resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
-
- semver@7.7.2:
- resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
- engines: {node: '>=10'}
- hasBin: true
-
- shebang-command@2.0.0:
- resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
- engines: {node: '>=8'}
-
- shebang-regex@3.0.0:
- resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
- engines: {node: '>=8'}
-
- siginfo@2.0.0:
- resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
-
- signal-exit@4.1.0:
- resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
- engines: {node: '>=14'}
-
- sort-object-keys@1.1.3:
- resolution: {integrity: sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==}
-
- sort-package-json@3.4.0:
- resolution: {integrity: sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA==}
- engines: {node: '>=20'}
- hasBin: true
-
- source-map-js@1.2.1:
- resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
- engines: {node: '>=0.10.0'}
-
- stackback@0.0.2:
- resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
-
- statuses@2.0.2:
- resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
- engines: {node: '>= 0.8'}
-
- std-env@3.9.0:
- resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
-
- strict-event-emitter@0.5.1:
- resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
-
- string-width@4.2.3:
- resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
- engines: {node: '>=8'}
-
- strip-ansi@6.0.1:
- resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
- engines: {node: '>=8'}
-
- strip-indent@3.0.0:
- resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
- engines: {node: '>=8'}
-
- strip-json-comments@3.1.1:
- resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
- engines: {node: '>=8'}
-
- strip-literal@3.0.0:
- resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
-
- supports-color@7.2.0:
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
- engines: {node: '>=8'}
-
- symbol-tree@3.2.4:
- resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
-
- tinybench@2.9.0:
- resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
-
- tinyexec@0.3.2:
- resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
-
- tinyglobby@0.2.15:
- resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
- engines: {node: '>=12.0.0'}
-
- tinypool@1.1.1:
- resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
- engines: {node: ^18.0.0 || >=20.0.0}
-
- tinyrainbow@2.0.0:
- resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
- engines: {node: '>=14.0.0'}
-
- tinyspy@4.0.4:
- resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
- engines: {node: '>=14.0.0'}
-
- tldts-core@6.1.86:
- resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
-
- tldts-core@7.0.16:
- resolution: {integrity: sha512-XHhPmHxphLi+LGbH0G/O7dmUH9V65OY20R7vH8gETHsp5AZCjBk9l8sqmRKLaGOxnETU7XNSDUPtewAy/K6jbA==}
-
- tldts@6.1.86:
- resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
- hasBin: true
-
- tldts@7.0.16:
- resolution: {integrity: sha512-5bdPHSwbKTeHmXrgecID4Ljff8rQjv7g8zKQPkCozRo2HWWni+p310FSn5ImI+9kWw9kK4lzOB5q/a6iv0IJsw==}
- hasBin: true
-
- to-regex-range@5.0.1:
- resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
- engines: {node: '>=8.0'}
-
- tough-cookie@5.1.2:
- resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
- engines: {node: '>=16'}
-
- tough-cookie@6.0.0:
- resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
- engines: {node: '>=16'}
-
- tr46@5.1.1:
- resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
- engines: {node: '>=18'}
-
- ts-api-utils@2.1.0:
- resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
- engines: {node: '>=18.12'}
- peerDependencies:
- typescript: '>=4.8.4'
-
- turbo-darwin-64@2.5.6:
- resolution: {integrity: sha512-3C1xEdo4aFwMJAPvtlPqz1Sw/+cddWIOmsalHFMrsqqydcptwBfu26WW2cDm3u93bUzMbBJ8k3zNKFqxJ9ei2A==}
- cpu: [x64]
- os: [darwin]
-
- turbo-darwin-arm64@2.5.6:
- resolution: {integrity: sha512-LyiG+rD7JhMfYwLqB6k3LZQtYn8CQQUePbpA8mF/hMLPAekXdJo1g0bUPw8RZLwQXUIU/3BU7tXENvhSGz5DPA==}
- cpu: [arm64]
- os: [darwin]
-
- turbo-linux-64@2.5.6:
- resolution: {integrity: sha512-GOcUTT0xiT/pSnHL4YD6Yr3HreUhU8pUcGqcI2ksIF9b2/r/kRHwGFcsHgpG3+vtZF/kwsP0MV8FTlTObxsYIA==}
- cpu: [x64]
- os: [linux]
-
- turbo-linux-arm64@2.5.6:
- resolution: {integrity: sha512-10Tm15bruJEA3m0V7iZcnQBpObGBcOgUcO+sY7/2vk1bweW34LMhkWi8svjV9iDF68+KJDThnYDlYE/bc7/zzQ==}
- cpu: [arm64]
- os: [linux]
-
- turbo-windows-64@2.5.6:
- resolution: {integrity: sha512-FyRsVpgaj76It0ludwZsNN40ytHN+17E4PFJyeliBEbxrGTc5BexlXVpufB7XlAaoaZVxbS6KT8RofLfDRyEPg==}
- cpu: [x64]
- os: [win32]
-
- turbo-windows-arm64@2.5.6:
- resolution: {integrity: sha512-j/tWu8cMeQ7HPpKri6jvKtyXg9K1gRyhdK4tKrrchH8GNHscPX/F71zax58yYtLRWTiK04zNzPcUJuoS0+v/+Q==}
- cpu: [arm64]
- os: [win32]
-
- turbo@2.5.6:
- resolution: {integrity: sha512-gxToHmi9oTBNB05UjUsrWf0OyN5ZXtD0apOarC1KIx232Vp3WimRNy3810QzeNSgyD5rsaIDXlxlbnOzlouo+w==}
- hasBin: true
-
- type-check@0.4.0:
- resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
- engines: {node: '>= 0.8.0'}
-
- type-fest@4.41.0:
- resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
- engines: {node: '>=16'}
-
- typescript-eslint@8.44.1:
- resolution: {integrity: sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0
- typescript: '>=4.8.4 <6.0.0'
-
- typescript@5.9.2:
- resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==}
- engines: {node: '>=14.17'}
- hasBin: true
-
- undici-types@7.12.0:
- resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==}
-
- until-async@3.0.2:
- resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==}
-
- uri-js@4.4.1:
- resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
-
- utf-8-validate@5.0.10:
- resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
- engines: {node: '>=6.14.2'}
-
- vite-node@3.2.4:
- resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
- engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
- hasBin: true
-
- vite@7.1.7:
- resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==}
- engines: {node: ^20.19.0 || >=22.12.0}
- hasBin: true
- peerDependencies:
- '@types/node': ^20.19.0 || >=22.12.0
- jiti: '>=1.21.0'
- less: ^4.0.0
- lightningcss: ^1.21.0
- sass: ^1.70.0
- sass-embedded: ^1.70.0
- stylus: '>=0.54.8'
- sugarss: ^5.0.0
- terser: ^5.16.0
- tsx: ^4.8.1
- yaml: ^2.4.2
- peerDependenciesMeta:
- '@types/node':
- optional: true
- jiti:
- optional: true
- less:
- optional: true
- lightningcss:
- optional: true
- sass:
- optional: true
- sass-embedded:
- optional: true
- stylus:
- optional: true
- sugarss:
- optional: true
- terser:
- optional: true
- tsx:
- optional: true
- yaml:
- optional: true
-
- vitest@3.2.4:
- resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
- engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
- hasBin: true
- peerDependencies:
- '@edge-runtime/vm': '*'
- '@types/debug': ^4.1.12
- '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
- '@vitest/browser': 3.2.4
- '@vitest/ui': 3.2.4
- happy-dom: '*'
- jsdom: '*'
- peerDependenciesMeta:
- '@edge-runtime/vm':
- optional: true
- '@types/debug':
- optional: true
- '@types/node':
- optional: true
- '@vitest/browser':
- optional: true
- '@vitest/ui':
- optional: true
- happy-dom:
- optional: true
- jsdom:
- optional: true
-
- w3c-xmlserializer@5.0.0:
- resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
- engines: {node: '>=18'}
-
- webidl-conversions@7.0.0:
- resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
- engines: {node: '>=12'}
-
- whatwg-encoding@3.1.1:
- resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
- engines: {node: '>=18'}
-
- whatwg-mimetype@4.0.0:
- resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
- engines: {node: '>=18'}
-
- whatwg-url@14.2.0:
- resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
- engines: {node: '>=18'}
-
- which@2.0.2:
- resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
- engines: {node: '>= 8'}
- hasBin: true
-
- why-is-node-running@2.3.0:
- resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
- engines: {node: '>=8'}
- hasBin: true
-
- word-wrap@1.2.5:
- resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
- engines: {node: '>=0.10.0'}
-
- wrap-ansi@6.2.0:
- resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
- engines: {node: '>=8'}
-
- wrap-ansi@7.0.0:
- resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
- engines: {node: '>=10'}
-
- ws@8.18.3:
- resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
- 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
-
- xml-name-validator@5.0.0:
- resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
- engines: {node: '>=18'}
-
- xmlchars@2.2.0:
- resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
-
- y18n@5.0.8:
- resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
- engines: {node: '>=10'}
-
- yargs-parser@21.1.1:
- resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
- engines: {node: '>=12'}
-
- yargs@17.7.2:
- resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
- engines: {node: '>=12'}
-
- yocto-queue@0.1.0:
- resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
- engines: {node: '>=10'}
-
- yoctocolors-cjs@2.1.3:
- resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==}
- engines: {node: '>=18'}
-
-snapshots:
-
- '@adobe/css-tools@4.4.4': {}
-
- '@asamuzakjp/css-color@3.2.0':
- dependencies:
- '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
- lru-cache: 10.4.3
-
- '@babel/code-frame@7.27.1':
- dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- js-tokens: 4.0.0
- picocolors: 1.1.1
-
- '@babel/helper-validator-identifier@7.27.1': {}
-
- '@babel/runtime@7.28.4': {}
-
- '@bundled-es-modules/cookie@2.0.1':
- dependencies:
- cookie: 0.7.2
- optional: true
-
- '@bundled-es-modules/statuses@1.0.1':
- dependencies:
- statuses: 2.0.2
- optional: true
-
- '@csstools/color-helpers@5.1.0': {}
-
- '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
- dependencies:
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
-
- '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
- dependencies:
- '@csstools/color-helpers': 5.1.0
- '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
- '@csstools/css-tokenizer': 3.0.4
-
- '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
- dependencies:
- '@csstools/css-tokenizer': 3.0.4
-
- '@csstools/css-tokenizer@3.0.4': {}
-
- '@esbuild/aix-ppc64@0.25.10':
- optional: true
-
- '@esbuild/android-arm64@0.25.10':
- optional: true
-
- '@esbuild/android-arm@0.25.10':
- optional: true
-
- '@esbuild/android-x64@0.25.10':
- optional: true
-
- '@esbuild/darwin-arm64@0.25.10':
- optional: true
-
- '@esbuild/darwin-x64@0.25.10':
- optional: true
-
- '@esbuild/freebsd-arm64@0.25.10':
- optional: true
-
- '@esbuild/freebsd-x64@0.25.10':
- optional: true
-
- '@esbuild/linux-arm64@0.25.10':
- optional: true
-
- '@esbuild/linux-arm@0.25.10':
- optional: true
-
- '@esbuild/linux-ia32@0.25.10':
- optional: true
-
- '@esbuild/linux-loong64@0.25.10':
- optional: true
-
- '@esbuild/linux-mips64el@0.25.10':
- optional: true
-
- '@esbuild/linux-ppc64@0.25.10':
- optional: true
-
- '@esbuild/linux-riscv64@0.25.10':
- optional: true
-
- '@esbuild/linux-s390x@0.25.10':
- optional: true
-
- '@esbuild/linux-x64@0.25.10':
- optional: true
-
- '@esbuild/netbsd-arm64@0.25.10':
- optional: true
-
- '@esbuild/netbsd-x64@0.25.10':
- optional: true
-
- '@esbuild/openbsd-arm64@0.25.10':
- optional: true
-
- '@esbuild/openbsd-x64@0.25.10':
- optional: true
-
- '@esbuild/openharmony-arm64@0.25.10':
- optional: true
-
- '@esbuild/sunos-x64@0.25.10':
- optional: true
-
- '@esbuild/win32-arm64@0.25.10':
- optional: true
-
- '@esbuild/win32-ia32@0.25.10':
- optional: true
-
- '@esbuild/win32-x64@0.25.10':
- optional: true
-
- '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)':
- dependencies:
- eslint: 9.36.0
- eslint-visitor-keys: 3.4.3
-
- '@eslint-community/regexpp@4.12.1': {}
-
- '@eslint/config-array@0.21.0':
- dependencies:
- '@eslint/object-schema': 2.1.6
- debug: 4.4.3
- minimatch: 3.1.2
- transitivePeerDependencies:
- - supports-color
-
- '@eslint/config-helpers@0.3.1': {}
-
- '@eslint/core@0.15.2':
- dependencies:
- '@types/json-schema': 7.0.15
-
- '@eslint/eslintrc@3.3.1':
- dependencies:
- ajv: 6.12.6
- debug: 4.4.3
- espree: 10.4.0
- globals: 14.0.0
- ignore: 5.3.2
- import-fresh: 3.3.1
- js-yaml: 4.1.0
- minimatch: 3.1.2
- strip-json-comments: 3.1.1
- transitivePeerDependencies:
- - supports-color
-
- '@eslint/js@9.36.0': {}
-
- '@eslint/object-schema@2.1.6': {}
-
- '@eslint/plugin-kit@0.3.5':
- dependencies:
- '@eslint/core': 0.15.2
- levn: 0.4.1
-
- '@humanfs/core@0.19.1': {}
-
- '@humanfs/node@0.16.7':
- dependencies:
- '@humanfs/core': 0.19.1
- '@humanwhocodes/retry': 0.4.3
-
- '@humanwhocodes/module-importer@1.0.1': {}
-
- '@humanwhocodes/retry@0.4.3': {}
-
- '@inquirer/ansi@1.0.0':
- optional: true
-
- '@inquirer/confirm@5.1.18(@types/node@24.5.2)':
- dependencies:
- '@inquirer/core': 10.2.2(@types/node@24.5.2)
- '@inquirer/type': 3.0.8(@types/node@24.5.2)
- optionalDependencies:
- '@types/node': 24.5.2
- optional: true
-
- '@inquirer/core@10.2.2(@types/node@24.5.2)':
- dependencies:
- '@inquirer/ansi': 1.0.0
- '@inquirer/figures': 1.0.13
- '@inquirer/type': 3.0.8(@types/node@24.5.2)
- cli-width: 4.1.0
- mute-stream: 2.0.0
- signal-exit: 4.1.0
- wrap-ansi: 6.2.0
- yoctocolors-cjs: 2.1.3
- optionalDependencies:
- '@types/node': 24.5.2
- optional: true
-
- '@inquirer/figures@1.0.13':
- optional: true
-
- '@inquirer/type@3.0.8(@types/node@24.5.2)':
- optionalDependencies:
- '@types/node': 24.5.2
- optional: true
-
- '@jridgewell/sourcemap-codec@1.5.5': {}
-
- '@mswjs/interceptors@0.39.6':
- dependencies:
- '@open-draft/deferred-promise': 2.2.0
- '@open-draft/logger': 0.3.0
- '@open-draft/until': 2.1.0
- is-node-process: 1.2.0
- outvariant: 1.4.3
- strict-event-emitter: 0.5.1
- optional: true
-
- '@nodelib/fs.scandir@2.1.5':
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
-
- '@nodelib/fs.stat@2.0.5': {}
-
- '@nodelib/fs.walk@1.2.8':
- dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.19.1
-
- '@open-draft/deferred-promise@2.2.0':
- optional: true
-
- '@open-draft/logger@0.3.0':
- dependencies:
- is-node-process: 1.2.0
- outvariant: 1.4.3
- optional: true
-
- '@open-draft/until@2.1.0':
- optional: true
-
- '@rollup/rollup-android-arm-eabi@4.52.2':
- optional: true
-
- '@rollup/rollup-android-arm64@4.52.2':
- optional: true
-
- '@rollup/rollup-darwin-arm64@4.52.2':
- optional: true
-
- '@rollup/rollup-darwin-x64@4.52.2':
- optional: true
-
- '@rollup/rollup-freebsd-arm64@4.52.2':
- optional: true
-
- '@rollup/rollup-freebsd-x64@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-arm-gnueabihf@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-arm-musleabihf@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-arm64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-arm64-musl@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-loong64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-ppc64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-riscv64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-riscv64-musl@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-s390x-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-x64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-linux-x64-musl@4.52.2':
- optional: true
-
- '@rollup/rollup-openharmony-arm64@4.52.2':
- optional: true
-
- '@rollup/rollup-win32-arm64-msvc@4.52.2':
- optional: true
-
- '@rollup/rollup-win32-ia32-msvc@4.52.2':
- optional: true
-
- '@rollup/rollup-win32-x64-gnu@4.52.2':
- optional: true
-
- '@rollup/rollup-win32-x64-msvc@4.52.2':
- optional: true
-
- '@testing-library/dom@10.4.1':
- dependencies:
- '@babel/code-frame': 7.27.1
- '@babel/runtime': 7.28.4
- '@types/aria-query': 5.0.4
- aria-query: 5.3.0
- dom-accessibility-api: 0.5.16
- lz-string: 1.5.0
- picocolors: 1.1.1
- pretty-format: 27.5.1
-
- '@testing-library/jest-dom@6.8.0':
- dependencies:
- '@adobe/css-tools': 4.4.4
- aria-query: 5.3.2
- css.escape: 1.5.1
- dom-accessibility-api: 0.6.3
- picocolors: 1.1.1
- redent: 3.0.0
-
- '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.24))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
- dependencies:
- '@babel/runtime': 7.28.4
- '@testing-library/dom': 10.4.1
- react: 18.3.1
- react-dom: 18.3.1(react@18.3.1)
- optionalDependencies:
- '@types/react': 18.3.24
- '@types/react-dom': 18.3.7(@types/react@18.3.24)
-
- '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
- dependencies:
- '@testing-library/dom': 10.4.1
-
- '@types/aria-query@5.0.4': {}
-
- '@types/chai@5.2.2':
- dependencies:
- '@types/deep-eql': 4.0.2
-
- '@types/cookie@0.6.0':
- optional: true
-
- '@types/deep-eql@4.0.2': {}
-
- '@types/estree@1.0.8': {}
-
- '@types/json-schema@7.0.15': {}
-
- '@types/node@24.5.2':
- dependencies:
- undici-types: 7.12.0
- optional: true
-
- '@types/prop-types@15.7.15': {}
-
- '@types/react-dom@18.3.7(@types/react@18.3.24)':
- dependencies:
- '@types/react': 18.3.24
-
- '@types/react@18.3.24':
- dependencies:
- '@types/prop-types': 15.7.15
- csstype: 3.1.3
-
- '@types/statuses@2.0.6':
- optional: true
-
- '@typescript-eslint/eslint-plugin@8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)':
- dependencies:
- '@eslint-community/regexpp': 4.12.1
- '@typescript-eslint/parser': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- '@typescript-eslint/scope-manager': 8.44.1
- '@typescript-eslint/type-utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- '@typescript-eslint/visitor-keys': 8.44.1
- eslint: 9.36.0
- graphemer: 1.4.0
- ignore: 7.0.5
- natural-compare: 1.4.0
- ts-api-utils: 2.1.0(typescript@5.9.2)
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/scope-manager': 8.44.1
- '@typescript-eslint/types': 8.44.1
- '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/visitor-keys': 8.44.1
- debug: 4.4.3
- eslint: 9.36.0
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/project-service@8.44.1(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/types': 8.44.1
- debug: 4.4.3
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/scope-manager@8.44.1':
- dependencies:
- '@typescript-eslint/types': 8.44.1
- '@typescript-eslint/visitor-keys': 8.44.1
-
- '@typescript-eslint/tsconfig-utils@8.44.1(typescript@5.9.2)':
- dependencies:
- typescript: 5.9.2
-
- '@typescript-eslint/type-utils@8.44.1(eslint@9.36.0)(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/types': 8.44.1
- '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- debug: 4.4.3
- eslint: 9.36.0
- ts-api-utils: 2.1.0(typescript@5.9.2)
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/types@8.44.1': {}
-
- '@typescript-eslint/typescript-estree@8.44.1(typescript@5.9.2)':
- dependencies:
- '@typescript-eslint/project-service': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/types': 8.44.1
- '@typescript-eslint/visitor-keys': 8.44.1
- debug: 4.4.3
- fast-glob: 3.3.3
- is-glob: 4.0.3
- minimatch: 9.0.5
- semver: 7.7.2
- ts-api-utils: 2.1.0(typescript@5.9.2)
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/utils@8.44.1(eslint@9.36.0)(typescript@5.9.2)':
- dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0)
- '@typescript-eslint/scope-manager': 8.44.1
- '@typescript-eslint/types': 8.44.1
- '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2)
- eslint: 9.36.0
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- '@typescript-eslint/visitor-keys@8.44.1':
- dependencies:
- '@typescript-eslint/types': 8.44.1
- eslint-visitor-keys: 4.2.1
-
- '@vitest/expect@3.2.4':
- dependencies:
- '@types/chai': 5.2.2
- '@vitest/spy': 3.2.4
- '@vitest/utils': 3.2.4
- chai: 5.3.3
- tinyrainbow: 2.0.0
-
- '@vitest/mocker@3.2.4(msw@2.11.3(@types/node@24.5.2)(typescript@5.9.2))(vite@7.1.7(@types/node@24.5.2))':
- dependencies:
- '@vitest/spy': 3.2.4
- estree-walker: 3.0.3
- magic-string: 0.30.19
- optionalDependencies:
- msw: 2.11.3(@types/node@24.5.2)(typescript@5.9.2)
- vite: 7.1.7(@types/node@24.5.2)
-
- '@vitest/pretty-format@3.2.4':
- dependencies:
- tinyrainbow: 2.0.0
-
- '@vitest/runner@3.2.4':
- dependencies:
- '@vitest/utils': 3.2.4
- pathe: 2.0.3
- strip-literal: 3.0.0
-
- '@vitest/snapshot@3.2.4':
- dependencies:
- '@vitest/pretty-format': 3.2.4
- magic-string: 0.30.19
- pathe: 2.0.3
-
- '@vitest/spy@3.2.4':
- dependencies:
- tinyspy: 4.0.4
-
- '@vitest/utils@3.2.4':
- dependencies:
- '@vitest/pretty-format': 3.2.4
- loupe: 3.2.1
- tinyrainbow: 2.0.0
-
- acorn-jsx@5.3.2(acorn@8.15.0):
- dependencies:
- acorn: 8.15.0
-
- acorn@8.15.0: {}
-
- agent-base@7.1.4: {}
-
- ajv@6.12.6:
- dependencies:
- fast-deep-equal: 3.1.3
- fast-json-stable-stringify: 2.1.0
- json-schema-traverse: 0.4.1
- uri-js: 4.4.1
-
- ansi-regex@5.0.1: {}
-
- ansi-styles@4.3.0:
- dependencies:
- color-convert: 2.0.1
-
- ansi-styles@5.2.0: {}
-
- argparse@2.0.1: {}
-
- aria-query@5.3.0:
- dependencies:
- dequal: 2.0.3
-
- aria-query@5.3.2: {}
-
- assertion-error@2.0.1: {}
-
- balanced-match@1.0.2: {}
-
- brace-expansion@1.1.12:
- dependencies:
- balanced-match: 1.0.2
- concat-map: 0.0.1
-
- brace-expansion@2.0.2:
- dependencies:
- balanced-match: 1.0.2
-
- braces@3.0.3:
- dependencies:
- fill-range: 7.1.1
-
- bufferutil@4.0.9:
- dependencies:
- node-gyp-build: 4.8.4
- optional: true
-
- cac@6.7.14: {}
-
- callsites@3.1.0: {}
-
- chai@5.3.3:
- dependencies:
- assertion-error: 2.0.1
- check-error: 2.1.1
- deep-eql: 5.0.2
- loupe: 3.2.1
- pathval: 2.0.1
-
- chalk@4.1.2:
- dependencies:
- ansi-styles: 4.3.0
- supports-color: 7.2.0
-
- check-error@2.1.1: {}
-
- cli-width@4.1.0:
- optional: true
-
- cliui@8.0.1:
- dependencies:
- string-width: 4.2.3
- strip-ansi: 6.0.1
- wrap-ansi: 7.0.0
- optional: true
-
- color-convert@2.0.1:
- dependencies:
- color-name: 1.1.4
-
- color-name@1.1.4: {}
-
- concat-map@0.0.1: {}
-
- cookie@0.7.2:
- optional: true
-
- cross-spawn@7.0.6:
- dependencies:
- path-key: 3.1.1
- shebang-command: 2.0.0
- which: 2.0.2
-
- css.escape@1.5.1: {}
-
- cssstyle@4.6.0:
- dependencies:
- '@asamuzakjp/css-color': 3.2.0
- rrweb-cssom: 0.8.0
-
- csstype@3.1.3: {}
-
- data-urls@5.0.0:
- dependencies:
- whatwg-mimetype: 4.0.0
- whatwg-url: 14.2.0
-
- debug@4.4.3:
- dependencies:
- ms: 2.1.3
-
- decimal.js@10.6.0: {}
-
- deep-eql@5.0.2: {}
-
- deep-is@0.1.4: {}
-
- dequal@2.0.3: {}
-
- detect-indent@7.0.2: {}
-
- detect-newline@4.0.1: {}
-
- dom-accessibility-api@0.5.16: {}
-
- dom-accessibility-api@0.6.3: {}
-
- emoji-regex@8.0.0:
- optional: true
-
- entities@6.0.1: {}
-
- es-module-lexer@1.7.0: {}
-
- esbuild@0.25.10:
- optionalDependencies:
- '@esbuild/aix-ppc64': 0.25.10
- '@esbuild/android-arm': 0.25.10
- '@esbuild/android-arm64': 0.25.10
- '@esbuild/android-x64': 0.25.10
- '@esbuild/darwin-arm64': 0.25.10
- '@esbuild/darwin-x64': 0.25.10
- '@esbuild/freebsd-arm64': 0.25.10
- '@esbuild/freebsd-x64': 0.25.10
- '@esbuild/linux-arm': 0.25.10
- '@esbuild/linux-arm64': 0.25.10
- '@esbuild/linux-ia32': 0.25.10
- '@esbuild/linux-loong64': 0.25.10
- '@esbuild/linux-mips64el': 0.25.10
- '@esbuild/linux-ppc64': 0.25.10
- '@esbuild/linux-riscv64': 0.25.10
- '@esbuild/linux-s390x': 0.25.10
- '@esbuild/linux-x64': 0.25.10
- '@esbuild/netbsd-arm64': 0.25.10
- '@esbuild/netbsd-x64': 0.25.10
- '@esbuild/openbsd-arm64': 0.25.10
- '@esbuild/openbsd-x64': 0.25.10
- '@esbuild/openharmony-arm64': 0.25.10
- '@esbuild/sunos-x64': 0.25.10
- '@esbuild/win32-arm64': 0.25.10
- '@esbuild/win32-ia32': 0.25.10
- '@esbuild/win32-x64': 0.25.10
-
- escalade@3.2.0:
- optional: true
-
- escape-string-regexp@4.0.0: {}
-
- eslint-scope@8.4.0:
- dependencies:
- esrecurse: 4.3.0
- estraverse: 5.3.0
-
- eslint-visitor-keys@3.4.3: {}
-
- eslint-visitor-keys@4.2.1: {}
-
- eslint@9.36.0:
- dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0)
- '@eslint-community/regexpp': 4.12.1
- '@eslint/config-array': 0.21.0
- '@eslint/config-helpers': 0.3.1
- '@eslint/core': 0.15.2
- '@eslint/eslintrc': 3.3.1
- '@eslint/js': 9.36.0
- '@eslint/plugin-kit': 0.3.5
- '@humanfs/node': 0.16.7
- '@humanwhocodes/module-importer': 1.0.1
- '@humanwhocodes/retry': 0.4.3
- '@types/estree': 1.0.8
- '@types/json-schema': 7.0.15
- ajv: 6.12.6
- chalk: 4.1.2
- cross-spawn: 7.0.6
- debug: 4.4.3
- escape-string-regexp: 4.0.0
- eslint-scope: 8.4.0
- eslint-visitor-keys: 4.2.1
- espree: 10.4.0
- esquery: 1.6.0
- esutils: 2.0.3
- fast-deep-equal: 3.1.3
- file-entry-cache: 8.0.0
- find-up: 5.0.0
- glob-parent: 6.0.2
- ignore: 5.3.2
- imurmurhash: 0.1.4
- is-glob: 4.0.3
- json-stable-stringify-without-jsonify: 1.0.1
- lodash.merge: 4.6.2
- minimatch: 3.1.2
- natural-compare: 1.4.0
- optionator: 0.9.4
- transitivePeerDependencies:
- - supports-color
-
- espree@10.4.0:
- dependencies:
- acorn: 8.15.0
- acorn-jsx: 5.3.2(acorn@8.15.0)
- eslint-visitor-keys: 4.2.1
-
- esquery@1.6.0:
- dependencies:
- estraverse: 5.3.0
-
- esrecurse@4.3.0:
- dependencies:
- estraverse: 5.3.0
-
- estraverse@5.3.0: {}
-
- estree-walker@3.0.3:
- dependencies:
- '@types/estree': 1.0.8
-
- esutils@2.0.3: {}
-
- expect-type@1.2.2: {}
-
- fast-deep-equal@3.1.3: {}
-
- fast-glob@3.3.3:
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- '@nodelib/fs.walk': 1.2.8
- glob-parent: 5.1.2
- merge2: 1.4.1
- micromatch: 4.0.8
-
- fast-json-stable-stringify@2.1.0: {}
-
- fast-levenshtein@2.0.6: {}
-
- fastq@1.19.1:
- dependencies:
- reusify: 1.1.0
-
- fdir@6.5.0(picomatch@4.0.3):
- optionalDependencies:
- picomatch: 4.0.3
-
- file-entry-cache@8.0.0:
- dependencies:
- flat-cache: 4.0.1
-
- fill-range@7.1.1:
- dependencies:
- to-regex-range: 5.0.1
-
- find-up@5.0.0:
- dependencies:
- locate-path: 6.0.0
- path-exists: 4.0.0
-
- flat-cache@4.0.1:
- dependencies:
- flatted: 3.3.3
- keyv: 4.5.4
-
- flatted@3.3.3: {}
-
- fsevents@2.3.3:
- optional: true
-
- get-caller-file@2.0.5:
- optional: true
-
- git-hooks-list@4.1.1: {}
-
- glob-parent@5.1.2:
- dependencies:
- is-glob: 4.0.3
-
- glob-parent@6.0.2:
- dependencies:
- is-glob: 4.0.3
-
- globals@14.0.0: {}
-
- graphemer@1.4.0: {}
-
- graphql@16.11.0:
- optional: true
-
- has-flag@4.0.0: {}
-
- headers-polyfill@4.0.3:
- optional: true
-
- html-encoding-sniffer@4.0.0:
- dependencies:
- whatwg-encoding: 3.1.1
-
- http-proxy-agent@7.0.2:
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
-
- https-proxy-agent@7.0.6:
- dependencies:
- agent-base: 7.1.4
- debug: 4.4.3
- transitivePeerDependencies:
- - supports-color
-
- iconv-lite@0.6.3:
- dependencies:
- safer-buffer: 2.1.2
-
- ignore@5.3.2: {}
-
- ignore@7.0.5: {}
-
- import-fresh@3.3.1:
- dependencies:
- parent-module: 1.0.1
- resolve-from: 4.0.0
-
- imurmurhash@0.1.4: {}
-
- indent-string@4.0.0: {}
-
- is-extglob@2.1.1: {}
-
- is-fullwidth-code-point@3.0.0:
- optional: true
-
- is-glob@4.0.3:
- dependencies:
- is-extglob: 2.1.1
-
- is-node-process@1.2.0:
- optional: true
-
- is-number@7.0.0: {}
-
- is-plain-obj@4.1.0: {}
-
- is-potential-custom-element-name@1.0.1: {}
-
- isexe@2.0.0: {}
-
- js-tokens@4.0.0: {}
-
- js-tokens@9.0.1: {}
-
- js-yaml@4.1.0:
- dependencies:
- argparse: 2.0.1
-
- jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10):
- dependencies:
- cssstyle: 4.6.0
- data-urls: 5.0.0
- decimal.js: 10.6.0
- html-encoding-sniffer: 4.0.0
- http-proxy-agent: 7.0.2
- https-proxy-agent: 7.0.6
- is-potential-custom-element-name: 1.0.1
- nwsapi: 2.2.22
- parse5: 7.3.0
- rrweb-cssom: 0.8.0
- saxes: 6.0.0
- symbol-tree: 3.2.4
- tough-cookie: 5.1.2
- w3c-xmlserializer: 5.0.0
- webidl-conversions: 7.0.0
- whatwg-encoding: 3.1.1
- whatwg-mimetype: 4.0.0
- whatwg-url: 14.2.0
- ws: 8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)
- xml-name-validator: 5.0.0
- transitivePeerDependencies:
- - bufferutil
- - supports-color
- - utf-8-validate
-
- json-buffer@3.0.1: {}
-
- json-schema-traverse@0.4.1: {}
-
- json-stable-stringify-without-jsonify@1.0.1: {}
-
- keyv@4.5.4:
- dependencies:
- json-buffer: 3.0.1
-
- levn@0.4.1:
- dependencies:
- prelude-ls: 1.2.1
- type-check: 0.4.0
-
- locate-path@6.0.0:
- dependencies:
- p-locate: 5.0.0
-
- lodash.merge@4.6.2: {}
-
- loose-envify@1.4.0:
- dependencies:
- js-tokens: 4.0.0
-
- loupe@3.2.1: {}
-
- lru-cache@10.4.3: {}
-
- lz-string@1.5.0: {}
-
- magic-string@0.30.19:
- dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
-
- merge2@1.4.1: {}
-
- micromatch@4.0.8:
- dependencies:
- braces: 3.0.3
- picomatch: 2.3.1
-
- min-indent@1.0.1: {}
-
- minimatch@3.1.2:
- dependencies:
- brace-expansion: 1.1.12
-
- minimatch@9.0.5:
- dependencies:
- brace-expansion: 2.0.2
-
- ms@2.1.3: {}
-
- msw@2.11.3(@types/node@24.5.2)(typescript@5.9.2):
- dependencies:
- '@bundled-es-modules/cookie': 2.0.1
- '@bundled-es-modules/statuses': 1.0.1
- '@inquirer/confirm': 5.1.18(@types/node@24.5.2)
- '@mswjs/interceptors': 0.39.6
- '@open-draft/deferred-promise': 2.2.0
- '@types/cookie': 0.6.0
- '@types/statuses': 2.0.6
- graphql: 16.11.0
- headers-polyfill: 4.0.3
- is-node-process: 1.2.0
- outvariant: 1.4.3
- path-to-regexp: 6.3.0
- picocolors: 1.1.1
- rettime: 0.7.0
- strict-event-emitter: 0.5.1
- tough-cookie: 6.0.0
- type-fest: 4.41.0
- until-async: 3.0.2
- yargs: 17.7.2
- optionalDependencies:
- typescript: 5.9.2
- transitivePeerDependencies:
- - '@types/node'
- optional: true
-
- mute-stream@2.0.0:
- optional: true
-
- nanoid@3.3.11: {}
-
- natural-compare@1.4.0: {}
-
- node-gyp-build@4.8.4:
- optional: true
-
- nwsapi@2.2.22: {}
-
- optionator@0.9.4:
- dependencies:
- deep-is: 0.1.4
- fast-levenshtein: 2.0.6
- levn: 0.4.1
- prelude-ls: 1.2.1
- type-check: 0.4.0
- word-wrap: 1.2.5
-
- outvariant@1.4.3:
- optional: true
-
- p-limit@3.1.0:
- dependencies:
- yocto-queue: 0.1.0
-
- p-locate@5.0.0:
- dependencies:
- p-limit: 3.1.0
-
- parent-module@1.0.1:
- dependencies:
- callsites: 3.1.0
-
- parse5@7.3.0:
- dependencies:
- entities: 6.0.1
-
- path-exists@4.0.0: {}
-
- path-key@3.1.1: {}
-
- path-to-regexp@6.3.0:
- optional: true
-
- pathe@2.0.3: {}
-
- pathval@2.0.1: {}
-
- picocolors@1.1.1: {}
-
- picomatch@2.3.1: {}
-
- picomatch@4.0.3: {}
-
- postcss@8.5.6:
- dependencies:
- nanoid: 3.3.11
- picocolors: 1.1.1
- source-map-js: 1.2.1
-
- prelude-ls@1.2.1: {}
-
- prettier@3.6.2: {}
-
- pretty-format@27.5.1:
- dependencies:
- ansi-regex: 5.0.1
- ansi-styles: 5.2.0
- react-is: 17.0.2
-
- punycode@2.3.1: {}
-
- queue-microtask@1.2.3: {}
-
- react-dom@18.3.1(react@18.3.1):
- dependencies:
- loose-envify: 1.4.0
- react: 18.3.1
- scheduler: 0.23.2
-
- react-is@17.0.2: {}
-
- react@18.3.1:
- dependencies:
- loose-envify: 1.4.0
-
- redent@3.0.0:
- dependencies:
- indent-string: 4.0.0
- strip-indent: 3.0.0
-
- require-directory@2.1.1:
- optional: true
-
- resolve-from@4.0.0: {}
-
- rettime@0.7.0:
- optional: true
-
- reusify@1.1.0: {}
-
- rollup@4.52.2:
- dependencies:
- '@types/estree': 1.0.8
- optionalDependencies:
- '@rollup/rollup-android-arm-eabi': 4.52.2
- '@rollup/rollup-android-arm64': 4.52.2
- '@rollup/rollup-darwin-arm64': 4.52.2
- '@rollup/rollup-darwin-x64': 4.52.2
- '@rollup/rollup-freebsd-arm64': 4.52.2
- '@rollup/rollup-freebsd-x64': 4.52.2
- '@rollup/rollup-linux-arm-gnueabihf': 4.52.2
- '@rollup/rollup-linux-arm-musleabihf': 4.52.2
- '@rollup/rollup-linux-arm64-gnu': 4.52.2
- '@rollup/rollup-linux-arm64-musl': 4.52.2
- '@rollup/rollup-linux-loong64-gnu': 4.52.2
- '@rollup/rollup-linux-ppc64-gnu': 4.52.2
- '@rollup/rollup-linux-riscv64-gnu': 4.52.2
- '@rollup/rollup-linux-riscv64-musl': 4.52.2
- '@rollup/rollup-linux-s390x-gnu': 4.52.2
- '@rollup/rollup-linux-x64-gnu': 4.52.2
- '@rollup/rollup-linux-x64-musl': 4.52.2
- '@rollup/rollup-openharmony-arm64': 4.52.2
- '@rollup/rollup-win32-arm64-msvc': 4.52.2
- '@rollup/rollup-win32-ia32-msvc': 4.52.2
- '@rollup/rollup-win32-x64-gnu': 4.52.2
- '@rollup/rollup-win32-x64-msvc': 4.52.2
- fsevents: 2.3.3
-
- rrweb-cssom@0.8.0: {}
-
- run-parallel@1.2.0:
- dependencies:
- queue-microtask: 1.2.3
-
- safer-buffer@2.1.2: {}
-
- saxes@6.0.0:
- dependencies:
- xmlchars: 2.2.0
-
- scheduler@0.23.2:
- dependencies:
- loose-envify: 1.4.0
-
- semver@7.7.2: {}
-
- shebang-command@2.0.0:
- dependencies:
- shebang-regex: 3.0.0
-
- shebang-regex@3.0.0: {}
-
- siginfo@2.0.0: {}
-
- signal-exit@4.1.0:
- optional: true
-
- sort-object-keys@1.1.3: {}
-
- sort-package-json@3.4.0:
- dependencies:
- detect-indent: 7.0.2
- detect-newline: 4.0.1
- git-hooks-list: 4.1.1
- is-plain-obj: 4.1.0
- semver: 7.7.2
- sort-object-keys: 1.1.3
- tinyglobby: 0.2.15
-
- source-map-js@1.2.1: {}
-
- stackback@0.0.2: {}
-
- statuses@2.0.2:
- optional: true
-
- std-env@3.9.0: {}
-
- strict-event-emitter@0.5.1:
- optional: true
-
- string-width@4.2.3:
- dependencies:
- emoji-regex: 8.0.0
- is-fullwidth-code-point: 3.0.0
- strip-ansi: 6.0.1
- optional: true
-
- strip-ansi@6.0.1:
- dependencies:
- ansi-regex: 5.0.1
- optional: true
-
- strip-indent@3.0.0:
- dependencies:
- min-indent: 1.0.1
-
- strip-json-comments@3.1.1: {}
-
- strip-literal@3.0.0:
- dependencies:
- js-tokens: 9.0.1
-
- supports-color@7.2.0:
- dependencies:
- has-flag: 4.0.0
-
- symbol-tree@3.2.4: {}
-
- tinybench@2.9.0: {}
-
- tinyexec@0.3.2: {}
-
- tinyglobby@0.2.15:
- dependencies:
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
-
- tinypool@1.1.1: {}
-
- tinyrainbow@2.0.0: {}
-
- tinyspy@4.0.4: {}
-
- tldts-core@6.1.86: {}
-
- tldts-core@7.0.16:
- optional: true
-
- tldts@6.1.86:
- dependencies:
- tldts-core: 6.1.86
-
- tldts@7.0.16:
- dependencies:
- tldts-core: 7.0.16
- optional: true
-
- to-regex-range@5.0.1:
- dependencies:
- is-number: 7.0.0
-
- tough-cookie@5.1.2:
- dependencies:
- tldts: 6.1.86
-
- tough-cookie@6.0.0:
- dependencies:
- tldts: 7.0.16
- optional: true
-
- tr46@5.1.1:
- dependencies:
- punycode: 2.3.1
-
- ts-api-utils@2.1.0(typescript@5.9.2):
- dependencies:
- typescript: 5.9.2
-
- turbo-darwin-64@2.5.6:
- optional: true
-
- turbo-darwin-arm64@2.5.6:
- optional: true
-
- turbo-linux-64@2.5.6:
- optional: true
-
- turbo-linux-arm64@2.5.6:
- optional: true
-
- turbo-windows-64@2.5.6:
- optional: true
-
- turbo-windows-arm64@2.5.6:
- optional: true
-
- turbo@2.5.6:
- optionalDependencies:
- turbo-darwin-64: 2.5.6
- turbo-darwin-arm64: 2.5.6
- turbo-linux-64: 2.5.6
- turbo-linux-arm64: 2.5.6
- turbo-windows-64: 2.5.6
- turbo-windows-arm64: 2.5.6
-
- type-check@0.4.0:
- dependencies:
- prelude-ls: 1.2.1
-
- type-fest@4.41.0:
- optional: true
-
- typescript-eslint@8.44.1(eslint@9.36.0)(typescript@5.9.2):
- dependencies:
- '@typescript-eslint/eslint-plugin': 8.44.1(@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2))(eslint@9.36.0)(typescript@5.9.2)
- '@typescript-eslint/parser': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2)
- '@typescript-eslint/utils': 8.44.1(eslint@9.36.0)(typescript@5.9.2)
- eslint: 9.36.0
- typescript: 5.9.2
- transitivePeerDependencies:
- - supports-color
-
- typescript@5.9.2: {}
-
- undici-types@7.12.0:
- optional: true
-
- until-async@3.0.2:
- optional: true
-
- uri-js@4.4.1:
- dependencies:
- punycode: 2.3.1
-
- utf-8-validate@5.0.10:
- dependencies:
- node-gyp-build: 4.8.4
- optional: true
-
- vite-node@3.2.4(@types/node@24.5.2):
- dependencies:
- cac: 6.7.14
- debug: 4.4.3
- es-module-lexer: 1.7.0
- pathe: 2.0.3
- vite: 7.1.7(@types/node@24.5.2)
- transitivePeerDependencies:
- - '@types/node'
- - jiti
- - less
- - lightningcss
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- - tsx
- - yaml
-
- vite@7.1.7(@types/node@24.5.2):
- dependencies:
- esbuild: 0.25.10
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
- postcss: 8.5.6
- rollup: 4.52.2
- tinyglobby: 0.2.15
- optionalDependencies:
- '@types/node': 24.5.2
- fsevents: 2.3.3
-
- vitest@3.2.4(@types/node@24.5.2)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@24.5.2)(typescript@5.9.2)):
- dependencies:
- '@types/chai': 5.2.2
- '@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@24.5.2)(typescript@5.9.2))(vite@7.1.7(@types/node@24.5.2))
- '@vitest/pretty-format': 3.2.4
- '@vitest/runner': 3.2.4
- '@vitest/snapshot': 3.2.4
- '@vitest/spy': 3.2.4
- '@vitest/utils': 3.2.4
- chai: 5.3.3
- debug: 4.4.3
- expect-type: 1.2.2
- magic-string: 0.30.19
- pathe: 2.0.3
- picomatch: 4.0.3
- std-env: 3.9.0
- tinybench: 2.9.0
- tinyexec: 0.3.2
- tinyglobby: 0.2.15
- tinypool: 1.1.1
- tinyrainbow: 2.0.0
- vite: 7.1.7(@types/node@24.5.2)
- vite-node: 3.2.4(@types/node@24.5.2)
- why-is-node-running: 2.3.0
- optionalDependencies:
- '@types/node': 24.5.2
- jsdom: 26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
- transitivePeerDependencies:
- - jiti
- - less
- - lightningcss
- - msw
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- - tsx
- - yaml
-
- w3c-xmlserializer@5.0.0:
- dependencies:
- xml-name-validator: 5.0.0
-
- webidl-conversions@7.0.0: {}
-
- whatwg-encoding@3.1.1:
- dependencies:
- iconv-lite: 0.6.3
-
- whatwg-mimetype@4.0.0: {}
-
- whatwg-url@14.2.0:
- dependencies:
- tr46: 5.1.1
- webidl-conversions: 7.0.0
-
- which@2.0.2:
- dependencies:
- isexe: 2.0.0
-
- why-is-node-running@2.3.0:
- dependencies:
- siginfo: 2.0.0
- stackback: 0.0.2
-
- word-wrap@1.2.5: {}
-
- wrap-ansi@6.2.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
- optional: true
-
- wrap-ansi@7.0.0:
- dependencies:
- ansi-styles: 4.3.0
- string-width: 4.2.3
- strip-ansi: 6.0.1
- optional: true
-
- ws@8.18.3(bufferutil@4.0.9)(utf-8-validate@5.0.10):
- optionalDependencies:
- bufferutil: 4.0.9
- utf-8-validate: 5.0.10
-
- xml-name-validator@5.0.0: {}
-
- xmlchars@2.2.0: {}
-
- y18n@5.0.8:
- optional: true
-
- yargs-parser@21.1.1:
- optional: true
-
- yargs@17.7.2:
- dependencies:
- cliui: 8.0.1
- escalade: 3.2.0
- get-caller-file: 2.0.5
- require-directory: 2.1.1
- string-width: 4.2.3
- y18n: 5.0.8
- yargs-parser: 21.1.1
- optional: true
-
- yocto-queue@0.1.0: {}
-
- yoctocolors-cjs@2.1.3:
- optional: true
diff --git a/tsconfig.json b/tsconfig.json
index 07bac83..966cae0 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,15 @@
{
"compilerOptions": {
- "target": "ES2022",
- "module": "ESNext",
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
- "moduleResolution": "bundler",
"allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
"jsx": "react-jsx",
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "noEmit": true,
"outDir": "dist",
- "strict": true,
"skipLibCheck": true,
- "noEmit": true,
- "baseUrl": "."
+ "strict": true,
+ "target": "ES2022",
+ "verbatimModuleSyntax": true
}
}