diff --git a/apps/i15-1/.gitignore b/apps/i15-1/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/apps/i15-1/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/apps/i15-1/helm/Chart.yaml b/apps/i15-1/helm/Chart.yaml
new file mode 100644
index 0000000..3b4f9db
--- /dev/null
+++ b/apps/i15-1/helm/Chart.yaml
@@ -0,0 +1,11 @@
+apiVersion: v2
+name: i15-1
+description: I15-1 UI
+type: application
+version: 0.5.0 # Bumped by CI job
+appVersion: "0.5.0" # Bumped by CI job
+
+dependencies:
+ - name: ui-base
+ repository: "file://../../../helm/"
+ version: 0.1.0
diff --git a/apps/i15-1/helm/values.yaml b/apps/i15-1/helm/values.yaml
new file mode 100644
index 0000000..61c3610
--- /dev/null
+++ b/apps/i15-1/helm/values.yaml
@@ -0,0 +1,17 @@
+ui-base:
+ name: i15-1
+ host: i15-1.diamond.ac.uk
+
+ image:
+ repository: ghcr.io/diamondlightsource/atlas/i15-1
+ tag: # Updated by CI job with latest release version
+
+ upstreams:
+ - id: blueapi
+ path: /api/
+ rewriteTarget: /
+ target:
+ external:
+ uri: http://i15-1-blueapi.diamond.ac.uk
+
+ identityProvider: prod
diff --git a/apps/i15-1/index.html b/apps/i15-1/index.html
new file mode 100644
index 0000000..1a2add8
--- /dev/null
+++ b/apps/i15-1/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ I15-1
+
+
+
+
+
+
diff --git a/apps/i15-1/package.json b/apps/i15-1/package.json
new file mode 100644
index 0000000..2612c09
--- /dev/null
+++ b/apps/i15-1/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@atlas/i15-1",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@diamondlightsource/sci-react-ui": "^0.2.0",
+ "@mui/icons-material": "^6.5.0",
+ "@mui/material": "<7.0.0"
+ },
+ "devDependencies": {
+ "@atlas/vitest-conf": "workspace:*",
+ "typescript": "~5.9.3",
+ "msw": "^2.10.4",
+ "vite": "^7.3.1",
+ "react-router-dom": "^7.7.1",
+ "vite-plugin-relay": "^2.1.0",
+ "@vitejs/plugin-react-swc": "^3.11.0",
+ "vitest": "*"
+ },
+ "msw": {
+ "workerDirectory": [
+ "public"
+ ]
+ },
+ "packageManager": "pnpm@10.12.3+sha512.467df2c586056165580ad6dfb54ceaad94c5a30f80893ebdec5a44c5aa73c205ae4a5bb9d5ed6bb84ea7c249ece786642bbb49d06a307df218d03da41c317417"
+}
diff --git a/apps/i15-1/public/diamond.svg b/apps/i15-1/public/diamond.svg
new file mode 100644
index 0000000..0af1a17
--- /dev/null
+++ b/apps/i15-1/public/diamond.svg
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/apps/i15-1/public/mockServiceWorker.js b/apps/i15-1/public/mockServiceWorker.js
new file mode 100644
index 0000000..65cdd31
--- /dev/null
+++ b/apps/i15-1/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/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx b/apps/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx
new file mode 100644
index 0000000..aacdc8b
--- /dev/null
+++ b/apps/i15-1/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/i15-1/src/components/NumberInput.tsx b/apps/i15-1/src/components/NumberInput.tsx
new file mode 100644
index 0000000..0835a59
--- /dev/null
+++ b/apps/i15-1/src/components/NumberInput.tsx
@@ -0,0 +1,124 @@
+import { useState } from "react";
+import { TextField } from "@mui/material";
+
+const Modes = {
+ /** Natural numbers from 0 to inf */
+ natural: /^([0-9]+)$/,
+ /** Integers from -inf to inf */
+ integer: /^[+\\-]?([0-9]+)$/,
+ /** Floating point numbers from -inf to inf, accepts values such as 1. and .1 as valid*/
+ floating:
+ /^[+\\-]?(([0-9]+)|([0-9]+[\\.])|([\\.][0-9]+)|([0-9]+[\\.][0-9]+))$/,
+ /** Floating point numbers from -inf to inf, accepts values such as 1.e1 and .1e1 as valid*/
+ scientific:
+ /^[+\\-]?(([0-9]+)|([0-9]+[\\.])|([\\.][0-9]+)|([0-9]+[\\.][0-9]+))([eE][+\\-]?[0-9]+)?$/,
+};
+
+interface NumberInputTextProps {
+ label: string;
+ numberMode: keyof typeof Modes;
+ numberText: string;
+ setNumberText: (v: string) => void;
+ isValid: boolean;
+ setIsValid: (v: boolean) => void;
+ handleCommit?: () => void;
+ commitOnReturn?: boolean;
+ commitOnBlur?: boolean;
+}
+
+const NumberInputText: React.FC = ({
+ label,
+ numberMode,
+ numberText,
+ setNumberText,
+ isValid,
+ setIsValid,
+ handleCommit,
+ commitOnReturn,
+ commitOnBlur,
+}) => {
+ const numberRegex = Modes[numberMode];
+
+ const handleInputChange = (value: string) => {
+ setIsValid(numberRegex.test(value));
+ setNumberText(value);
+ };
+
+ const handleKeyDown = (event: { key: string }) => {
+ if (event.key === "Enter" && commitOnReturn && isValid && handleCommit) {
+ handleCommit();
+ }
+ };
+
+ const handleBlur = () => {
+ if (isValid && commitOnBlur && handleCommit) {
+ handleCommit();
+ }
+ };
+
+ return (
+ handleInputChange(e.target.value)}
+ onKeyDown={handleKeyDown}
+ onBlur={handleBlur}
+ error={!isValid}
+ helperText={!isValid ? "Invalid input" : ""}
+ variant="outlined"
+ />
+ );
+};
+
+interface NumberInputProps {
+ label: string;
+ numberMode: keyof typeof Modes;
+ defaultValue: number | string;
+ onCommit?: (number: number) => void;
+ number?: number;
+ parameters?: object;
+ commitOnReturn?: boolean;
+ commitOnBlur?: boolean;
+}
+
+const NumberInput: React.FC = ({
+ label,
+ numberMode = "floating",
+ defaultValue,
+ onCommit,
+ commitOnReturn = true,
+ commitOnBlur = true,
+}) => {
+ const [numberText, setNumberText] = useState(defaultValue.toString());
+ const [isValid, setIsValid] = useState(
+ Modes[numberMode].test(defaultValue.toString()),
+ );
+
+ const handleCommit = () => {
+ const parsedValue: number = parseFloat(numberText);
+ if (onCommit) {
+ onCommit(parsedValue);
+ }
+ };
+
+ return (
+ <>
+ {
+
+ }
+ >
+ );
+};
+
+export { NumberInput };
+export type { NumberInputProps };
diff --git a/apps/i15-1/src/components/RunPlanButton.tsx b/apps/i15-1/src/components/RunPlanButton.tsx
new file mode 100644
index 0000000..86f20dc
--- /dev/null
+++ b/apps/i15-1/src/components/RunPlanButton.tsx
@@ -0,0 +1,41 @@
+import { Button } from "@mui/material";
+import { useEffect, useState } from "react";
+
+import { createAndStartTask, type TaskRequest } from "../utils/api";
+
+type RunPlanButtonProps = {
+ name: string;
+ params: object;
+ instrumentSession: string;
+ buttonText?: string;
+};
+
+const RunPlanButton = ({
+ name,
+ params,
+ instrumentSession,
+ buttonText = "Run",
+}: RunPlanButtonProps) => {
+ const [loading, setLoading] = useState(false);
+ return (
+
+ );
+};
+
+export default RunPlanButton;
diff --git a/apps/i15-1/src/components/WaffleNavbar.tsx b/apps/i15-1/src/components/WaffleNavbar.tsx
new file mode 100644
index 0000000..9d40a38
--- /dev/null
+++ b/apps/i15-1/src/components/WaffleNavbar.tsx
@@ -0,0 +1,42 @@
+import { Box } from "@mui/material";
+import { Link } from "react-router-dom";
+import {
+ ColourSchemeButton,
+ Navbar,
+ NavLink,
+ NavLinks,
+} from "@diamondlightsource/sci-react-ui";
+
+function WaffleNavbar() {
+ return (
+
+
+
+ Robot
+
+
+
+ }
+ rightSlot={
+
+
+
+ }
+ />
+ );
+}
+
+export default WaffleNavbar;
diff --git a/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts b/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts
new file mode 100644
index 0000000..74a9c93
--- /dev/null
+++ b/apps/i15-1/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/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx b/apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx
new file mode 100644
index 0000000..cf91adc
--- /dev/null
+++ b/apps/i15-1/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 = "cm12345-1",
+}: {
+ 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/i15-1/src/context/instrumentSession/useInstrumentSession.ts b/apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts
new file mode 100644
index 0000000..2b19f86
--- /dev/null
+++ b/apps/i15-1/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/i15-1/src/main.tsx b/apps/i15-1/src/main.tsx
new file mode 100644
index 0000000..dbdead3
--- /dev/null
+++ b/apps/i15-1/src/main.tsx
@@ -0,0 +1,51 @@
+import { DiamondTheme, ThemeProvider } from "@diamondlightsource/sci-react-ui";
+import { RouterProvider, createBrowserRouter } from "react-router-dom";
+import { createRoot } from "react-dom/client";
+import { StrictMode } from "react";
+
+import { Layout } from "./routes/Layout.tsx";
+import Dashboard from "./routes/Dashboard.tsx";
+import Robot from "./routes/Robot.tsx";
+import { InstrumentSessionProvider } from "./context/instrumentSession/InstrumentSessionProvider.tsx";
+
+declare global {
+ interface Window {
+ global?: typeof globalThis;
+ }
+}
+
+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: "robot",
+ element: ,
+ },
+ ],
+ },
+]);
+
+enableMocking().then(() => {
+ createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+
+ ,
+ );
+});
diff --git a/apps/i15-1/src/mocks/browser.ts b/apps/i15-1/src/mocks/browser.ts
new file mode 100644
index 0000000..bcd82e4
--- /dev/null
+++ b/apps/i15-1/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/i15-1/src/mocks/handlers.ts b/apps/i15-1/src/mocks/handlers.ts
new file mode 100644
index 0000000..a2a3aa7
--- /dev/null
+++ b/apps/i15-1/src/mocks/handlers.ts
@@ -0,0 +1,21 @@
+import { http, HttpResponse, graphql } from "msw";
+
+const fakeTaskId = "7304e8e0-81c6-4978-9a9d-9046ab79ce3c";
+
+export const handlers = [
+ http.put("/api/worker/task", () => {
+ return HttpResponse.json({
+ task_id: fakeTaskId,
+ });
+ }),
+
+ http.post("/api/tasks", () => {
+ return HttpResponse.json({
+ task_id: fakeTaskId,
+ });
+ }),
+
+ http.put("/api/worker/state", () => {
+ return HttpResponse.json("IDLE");
+ }),
+];
diff --git a/apps/i15-1/src/routes/Dashboard.tsx b/apps/i15-1/src/routes/Dashboard.tsx
new file mode 100644
index 0000000..4a5165d
--- /dev/null
+++ b/apps/i15-1/src/routes/Dashboard.tsx
@@ -0,0 +1,41 @@
+import { Container, Typography, Button, Stack } from "@mui/material";
+import { Link } from "react-router-dom";
+import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing";
+
+import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView.tsx";
+
+function Dashboard() {
+ return (
+ <>
+
+
+
+ Welcome to I15-1
+
+
+
+ }
+ sx={{ width: 150, height: 50 }}
+ >
+ Robot
+
+
+
+
+ >
+ );
+}
+
+export default Dashboard;
diff --git a/apps/i15-1/src/routes/Layout.tsx b/apps/i15-1/src/routes/Layout.tsx
new file mode 100644
index 0000000..29a40e9
--- /dev/null
+++ b/apps/i15-1/src/routes/Layout.tsx
@@ -0,0 +1,15 @@
+import { Link, Outlet, useLocation } from "react-router-dom";
+import WaffleNavbar from "../components/WaffleNavbar";
+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/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx
new file mode 100644
index 0000000..0ceef1a
--- /dev/null
+++ b/apps/i15-1/src/routes/Robot.tsx
@@ -0,0 +1,73 @@
+import { useInstrumentSession } from "../context/instrumentSession/useInstrumentSession";
+import { Box, Typography, Stack } from "@mui/material";
+import { useState } from "react";
+import { NumberInput } from "../components/NumberInput";
+import RunPlanButton from "../components/RunPlanButton";
+
+type RobotSampleFormData = {
+ puck: number;
+ position: number;
+};
+
+function Robot() {
+ const { instrumentSession, setInstrumentSession } = useInstrumentSession();
+ const [formData, setFormData] = useState({
+ puck: 1,
+ position: 1,
+ });
+ return (
+ <>
+
+
+
+ Sample Position
+
+
+ {
+ setFormData({ ...formData, ["puck"]: parsedValue });
+ }}
+ />
+ {
+ setFormData({ ...formData, ["position"]: parsedValue });
+ }}
+ />
+
+
+
+
+
+ >
+ );
+}
+
+export default Robot;
diff --git a/apps/i15-1/src/style.css b/apps/i15-1/src/style.css
new file mode 100644
index 0000000..3bcdbd0
--- /dev/null
+++ b/apps/i15-1/src/style.css
@@ -0,0 +1,96 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+#app {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #3178c6aa);
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/apps/i15-1/src/utils/api.ts b/apps/i15-1/src/utils/api.ts
new file mode 100644
index 0000000..81343ed
--- /dev/null
+++ b/apps/i15-1/src/utils/api.ts
@@ -0,0 +1,86 @@
+export interface TaskResponse {
+ task_id: string;
+}
+
+export interface TaskRequest {
+ name: string;
+ params: object;
+ instrument_session: string;
+}
+
+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();
+}
+
+export interface WorkerRequest {
+ new_state: string;
+ defer: boolean;
+ reason: string;
+}
+
+export interface WorkerResponse {
+ worker_state: string;
+}
+
+export async function setWorkerState(
+ worker_request: WorkerRequest,
+): Promise {
+ const url = "/api/worker/state";
+
+ 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({
+ new_state: worker_request.new_state,
+ defer: worker_request.defer,
+ reason: worker_request.reason,
+ }),
+ });
+
+ return await response.json();
+}
diff --git a/apps/i15-1/tsconfig.json b/apps/i15-1/tsconfig.json
new file mode 100644
index 0000000..8657541
--- /dev/null
+++ b/apps/i15-1/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["vite/client", "@atlas/vitest-conf/global-types"]
+ },
+ "include": ["src"]
+}
diff --git a/apps/i15-1/vite.config.ts b/apps/i15-1/vite.config.ts
new file mode 100644
index 0000000..fab2919
--- /dev/null
+++ b/apps/i15-1/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/pnpm-lock.yaml b/pnpm-lock.yaml
index 0352cf5..4002ddb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,11 +46,48 @@ importers:
specifier: ^4.0.13
version: 4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))
+ apps/i15-1:
+ dependencies:
+ '@diamondlightsource/sci-react-ui':
+ specifier: ^0.2.0
+ version: 0.2.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@mui/icons-material@6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
+ '@mui/icons-material':
+ specifier: ^6.5.0
+ version: 6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.24)(react@18.3.1)
+ '@mui/material':
+ specifier: <7.0.0
+ version: 6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ devDependencies:
+ '@atlas/vitest-conf':
+ specifier: workspace:*
+ version: link:../../packages/vitest-conf
+ '@vitejs/plugin-react-swc':
+ specifier: ^3.11.0
+ version: 3.11.0(vite@7.3.1(@types/node@25.0.10))
+ msw:
+ specifier: ^2.10.4
+ version: 2.11.3(@types/node@25.0.10)(typescript@5.9.3)
+ react-router-dom:
+ specifier: ^7.7.1
+ version: 7.13.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ typescript:
+ specifier: ~5.9.3
+ version: 5.9.3
+ vite:
+ specifier: ^7.3.1
+ version: 7.3.1(@types/node@25.0.10)
+ vite-plugin-relay:
+ specifier: ^2.1.0
+ version: 2.1.0(babel-plugin-relay@20.1.1)(vite@7.3.1(@types/node@25.0.10))
+ vitest:
+ specifier: '*'
+ version: 4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))
+
apps/visr:
dependencies:
'@diamondlightsource/davidia':
specifier: ^1.0.3
- version: 1.0.4(@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.2))(@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(@types/three@0.182.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(use-sync-external-store@1.6.0(react@18.3.1)))(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(ndarray@1.0.19)(react-dom@18.3.1(react@18.3.1))(react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(three@0.167.1)
+ version: 1.0.4(@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.3))(@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(@types/three@0.182.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(use-sync-external-store@1.6.0(react@18.3.1)))(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(ndarray@1.0.19)(react-dom@18.3.1(react@18.3.1))(react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(three@0.167.1)
'@diamondlightsource/sci-react-ui':
specifier: ^0.2.0
version: 0.2.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@mui/icons-material@6.5.0(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react@18.3.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
@@ -126,7 +163,7 @@ importers:
version: 16.5.0
msw:
specifier: ^2.10.4
- version: 2.11.3(@types/node@25.0.10)(typescript@5.9.2)
+ version: 2.11.3(@types/node@25.0.10)(typescript@5.9.3)
react-relay:
specifier: ^20.1.1
version: 20.1.1(react@18.3.1)
@@ -144,13 +181,13 @@ importers:
version: 2.1.0(babel-plugin-relay@20.1.1)(vite@7.1.7(@types/node@25.0.10))
vitest:
specifier: '*'
- version: 4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))
+ version: 4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))
packages/vitest-conf:
dependencies:
vitest:
specifier: '*'
- version: 3.2.4(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))
+ version: 3.2.4(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))
devDependencies:
'@testing-library/dom':
specifier: ^10.4.1
@@ -387,156 +424,312 @@ packages:
cpu: [ppc64]
os: [aix]
+ '@esbuild/aix-ppc64@0.27.3':
+ resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
+ 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-arm@0.27.3':
+ resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
+ 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/android-x64@0.27.3':
+ resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
+ 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/darwin-x64@0.27.3':
+ resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
+ 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/freebsd-x64@0.27.3':
+ resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
+ 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-arm@0.27.3':
+ resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
+ 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-ia32@0.27.3':
+ resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
+ 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-loong64@0.27.3':
+ resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
+ 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-mips64el@0.27.3':
+ resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
+ 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-ppc64@0.27.3':
+ resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
+ 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-riscv64@0.27.3':
+ resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
+ 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-s390x@0.27.3':
+ resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
+ 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/linux-x64@0.27.3':
+ resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
+ 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/netbsd-x64@0.27.3':
+ resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
+ 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/openbsd-x64@0.27.3':
+ resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
+ 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/openharmony-arm64@0.27.3':
+ resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
+ 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/sunos-x64@0.27.3':
+ resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
+ 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-arm64@0.27.3':
+ resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
+ 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-ia32@0.27.3':
+ resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
+ 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]
+ '@esbuild/win32-x64@0.27.3':
+ resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
+ 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}
@@ -1976,6 +2169,11 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ esbuild@0.27.3:
+ resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -3232,6 +3430,11 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
ua-parser-js@1.0.41:
resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==}
hasBin: true
@@ -3358,6 +3561,46 @@ packages:
yaml:
optional: true
+ vite@7.3.1:
+ resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
+ 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}
@@ -3763,9 +4006,9 @@ snapshots:
optionalDependencies:
dayjs: 1.10.7
- '@diamondlightsource/davidia@1.0.4(@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.2))(@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(@types/three@0.182.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(use-sync-external-store@1.6.0(react@18.3.1)))(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(ndarray@1.0.19)(react-dom@18.3.1(react@18.3.1))(react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(three@0.167.1)':
+ '@diamondlightsource/davidia@1.0.4(@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.3))(@react-three/drei@9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(@types/three@0.182.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(use-sync-external-store@1.6.0(react@18.3.1)))(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(ndarray@1.0.19)(react-dom@18.3.1(react@18.3.1))(react-toastify@9.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(three@0.167.1)':
dependencies:
- '@h5web/lib': 13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.2)
+ '@h5web/lib': 13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.3)
'@react-hookz/web': 24.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-three/drei': 9.122.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(@types/three@0.182.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(use-sync-external-store@1.6.0(react@18.3.1))
'@react-three/fiber': 8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)
@@ -3892,81 +4135,159 @@ snapshots:
'@esbuild/aix-ppc64@0.25.10':
optional: true
+ '@esbuild/aix-ppc64@0.27.3':
+ optional: true
+
'@esbuild/android-arm64@0.25.10':
optional: true
+ '@esbuild/android-arm64@0.27.3':
+ optional: true
+
'@esbuild/android-arm@0.25.10':
optional: true
+ '@esbuild/android-arm@0.27.3':
+ optional: true
+
'@esbuild/android-x64@0.25.10':
optional: true
+ '@esbuild/android-x64@0.27.3':
+ optional: true
+
'@esbuild/darwin-arm64@0.25.10':
optional: true
+ '@esbuild/darwin-arm64@0.27.3':
+ optional: true
+
'@esbuild/darwin-x64@0.25.10':
optional: true
+ '@esbuild/darwin-x64@0.27.3':
+ optional: true
+
'@esbuild/freebsd-arm64@0.25.10':
optional: true
+ '@esbuild/freebsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/freebsd-x64@0.25.10':
optional: true
+ '@esbuild/freebsd-x64@0.27.3':
+ optional: true
+
'@esbuild/linux-arm64@0.25.10':
optional: true
+ '@esbuild/linux-arm64@0.27.3':
+ optional: true
+
'@esbuild/linux-arm@0.25.10':
optional: true
+ '@esbuild/linux-arm@0.27.3':
+ optional: true
+
'@esbuild/linux-ia32@0.25.10':
optional: true
+ '@esbuild/linux-ia32@0.27.3':
+ optional: true
+
'@esbuild/linux-loong64@0.25.10':
optional: true
+ '@esbuild/linux-loong64@0.27.3':
+ optional: true
+
'@esbuild/linux-mips64el@0.25.10':
optional: true
+ '@esbuild/linux-mips64el@0.27.3':
+ optional: true
+
'@esbuild/linux-ppc64@0.25.10':
optional: true
+ '@esbuild/linux-ppc64@0.27.3':
+ optional: true
+
'@esbuild/linux-riscv64@0.25.10':
optional: true
+ '@esbuild/linux-riscv64@0.27.3':
+ optional: true
+
'@esbuild/linux-s390x@0.25.10':
optional: true
+ '@esbuild/linux-s390x@0.27.3':
+ optional: true
+
'@esbuild/linux-x64@0.25.10':
optional: true
+ '@esbuild/linux-x64@0.27.3':
+ optional: true
+
'@esbuild/netbsd-arm64@0.25.10':
optional: true
+ '@esbuild/netbsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/netbsd-x64@0.25.10':
optional: true
+ '@esbuild/netbsd-x64@0.27.3':
+ optional: true
+
'@esbuild/openbsd-arm64@0.25.10':
optional: true
+ '@esbuild/openbsd-arm64@0.27.3':
+ optional: true
+
'@esbuild/openbsd-x64@0.25.10':
optional: true
+ '@esbuild/openbsd-x64@0.27.3':
+ optional: true
+
'@esbuild/openharmony-arm64@0.25.10':
optional: true
+ '@esbuild/openharmony-arm64@0.27.3':
+ optional: true
+
'@esbuild/sunos-x64@0.25.10':
optional: true
+ '@esbuild/sunos-x64@0.27.3':
+ optional: true
+
'@esbuild/win32-arm64@0.25.10':
optional: true
+ '@esbuild/win32-arm64@0.27.3':
+ optional: true
+
'@esbuild/win32-ia32@0.25.10':
optional: true
+ '@esbuild/win32-ia32@0.27.3':
+ optional: true
+
'@esbuild/win32-x64@0.25.10':
optional: true
+ '@esbuild/win32-x64@0.27.3':
+ optional: true
+
'@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)':
dependencies:
eslint: 9.36.0
@@ -4036,7 +4357,7 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
- '@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.2)':
+ '@h5web/lib@13.0.0(@react-three/fiber@8.18.0(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1))(@types/react@18.3.24)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.167.1)(typescript@5.9.3)':
dependencies:
'@floating-ui/react': 0.26.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-hookz/web': 24.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -4065,7 +4386,7 @@ snapshots:
three: 0.167.1
zustand: 4.5.4(@types/react@18.3.24)(react@18.3.1)
optionalDependencies:
- typescript: 5.9.2
+ typescript: 5.9.3
transitivePeerDependencies:
- '@types/react'
- immer
@@ -4961,6 +5282,14 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
+ '@vitejs/plugin-react-swc@3.11.0(vite@7.3.1(@types/node@25.0.10))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@swc/core': 1.15.11
+ vite: 7.3.1(@types/node@25.0.10)
+ transitivePeerDependencies:
+ - '@swc/helpers'
+
'@vitest/expect@3.2.4':
dependencies:
'@types/chai': 5.2.2
@@ -4978,23 +5307,32 @@ snapshots:
chai: 6.2.1
tinyrainbow: 3.0.3
- '@vitest/mocker@3.2.4(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.1.7(@types/node@25.0.10))':
+ '@vitest/mocker@3.2.4(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- msw: 2.11.3(@types/node@25.0.10)(typescript@5.9.2)
- vite: 7.1.7(@types/node@25.0.10)
+ msw: 2.11.3(@types/node@25.0.10)(typescript@5.9.3)
+ vite: 7.3.1(@types/node@25.0.10)
- '@vitest/mocker@4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.1.7(@types/node@25.0.10))':
+ '@vitest/mocker@4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.3.1(@types/node@25.0.10))':
dependencies:
'@vitest/spy': 4.0.13
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
msw: 2.11.3(@types/node@25.0.10)(typescript@5.9.2)
- vite: 7.1.7(@types/node@25.0.10)
+ vite: 7.3.1(@types/node@25.0.10)
+
+ '@vitest/mocker@4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10))':
+ dependencies:
+ '@vitest/spy': 4.0.13
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ msw: 2.11.3(@types/node@25.0.10)(typescript@5.9.3)
+ vite: 7.3.1(@types/node@25.0.10)
'@vitest/pretty-format@3.2.4':
dependencies:
@@ -5497,6 +5835,35 @@ snapshots:
'@esbuild/win32-ia32': 0.25.10
'@esbuild/win32-x64': 0.25.10
+ esbuild@0.27.3:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.27.3
+ '@esbuild/android-arm': 0.27.3
+ '@esbuild/android-arm64': 0.27.3
+ '@esbuild/android-x64': 0.27.3
+ '@esbuild/darwin-arm64': 0.27.3
+ '@esbuild/darwin-x64': 0.27.3
+ '@esbuild/freebsd-arm64': 0.27.3
+ '@esbuild/freebsd-x64': 0.27.3
+ '@esbuild/linux-arm': 0.27.3
+ '@esbuild/linux-arm64': 0.27.3
+ '@esbuild/linux-ia32': 0.27.3
+ '@esbuild/linux-loong64': 0.27.3
+ '@esbuild/linux-mips64el': 0.27.3
+ '@esbuild/linux-ppc64': 0.27.3
+ '@esbuild/linux-riscv64': 0.27.3
+ '@esbuild/linux-s390x': 0.27.3
+ '@esbuild/linux-x64': 0.27.3
+ '@esbuild/netbsd-arm64': 0.27.3
+ '@esbuild/netbsd-x64': 0.27.3
+ '@esbuild/openbsd-arm64': 0.27.3
+ '@esbuild/openbsd-x64': 0.27.3
+ '@esbuild/openharmony-arm64': 0.27.3
+ '@esbuild/sunos-x64': 0.27.3
+ '@esbuild/win32-arm64': 0.27.3
+ '@esbuild/win32-ia32': 0.27.3
+ '@esbuild/win32-x64': 0.27.3
+
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
@@ -6018,6 +6385,33 @@ snapshots:
typescript: 5.9.2
transitivePeerDependencies:
- '@types/node'
+ optional: true
+
+ msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3):
+ dependencies:
+ '@bundled-es-modules/cookie': 2.0.1
+ '@bundled-es-modules/statuses': 1.0.1
+ '@inquirer/confirm': 5.1.18(@types/node@25.0.10)
+ '@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.3
+ transitivePeerDependencies:
+ - '@types/node'
mute-stream@2.0.0: {}
@@ -6725,6 +7119,8 @@ snapshots:
typescript@5.9.2: {}
+ typescript@5.9.3: {}
+
ua-parser-js@1.0.41: {}
uglify-js@2.8.29:
@@ -6795,7 +7191,7 @@ snapshots:
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 7.1.7(@types/node@25.0.10)
+ vite: 7.3.1(@types/node@25.0.10)
transitivePeerDependencies:
- '@types/node'
- jiti
@@ -6818,6 +7214,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ vite-plugin-relay@2.1.0(babel-plugin-relay@20.1.1)(vite@7.3.1(@types/node@25.0.10)):
+ dependencies:
+ '@babel/core': 7.28.6
+ babel-plugin-relay: 20.1.1
+ vite: 7.3.1(@types/node@25.0.10)
+ transitivePeerDependencies:
+ - supports-color
+
vite@7.1.7(@types/node@25.0.10):
dependencies:
esbuild: 0.25.10
@@ -6830,11 +7234,23 @@ snapshots:
'@types/node': 25.0.10
fsevents: 2.3.3
- vitest@3.2.4(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2)):
+ vite@7.3.1(@types/node@25.0.10):
+ dependencies:
+ esbuild: 0.27.3
+ 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': 25.0.10
+ fsevents: 2.3.3
+
+ vitest@3.2.4(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3)):
dependencies:
'@types/chai': 5.2.2
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.1.7(@types/node@25.0.10))
+ '@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@@ -6852,7 +7268,7 @@ snapshots:
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 7.1.7(@types/node@25.0.10)
+ vite: 7.3.1(@types/node@25.0.10)
vite-node: 3.2.4(@types/node@25.0.10)
why-is-node-running: 2.3.0
optionalDependencies:
@@ -6875,7 +7291,7 @@ snapshots:
vitest@4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2)):
dependencies:
'@vitest/expect': 4.0.13
- '@vitest/mocker': 4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.1.7(@types/node@25.0.10))
+ '@vitest/mocker': 4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.2))(vite@7.3.1(@types/node@25.0.10))
'@vitest/pretty-format': 4.0.13
'@vitest/runner': 4.0.13
'@vitest/snapshot': 4.0.13
@@ -6892,7 +7308,46 @@ snapshots:
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
- vite: 7.1.7(@types/node@25.0.10)
+ vite: 7.3.1(@types/node@25.0.10)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 25.0.10
+ 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
+
+ vitest@4.0.13(@types/node@25.0.10)(jsdom@26.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10))(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3)):
+ dependencies:
+ '@vitest/expect': 4.0.13
+ '@vitest/mocker': 4.0.13(msw@2.11.3(@types/node@25.0.10)(typescript@5.9.3))(vite@7.3.1(@types/node@25.0.10))
+ '@vitest/pretty-format': 4.0.13
+ '@vitest/runner': 4.0.13
+ '@vitest/snapshot': 4.0.13
+ '@vitest/spy': 4.0.13
+ '@vitest/utils': 4.0.13
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ expect-type: 1.2.2
+ magic-string: 0.30.21
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.0.3
+ vite: 7.3.1(@types/node@25.0.10)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.0.10