From 60e2e7e651e84e0b6923943a79d74ca44503f066 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 18 Feb 2026 11:25:03 +0000 Subject: [PATCH 1/5] add initial i15-1 app --- pnpm-lock.yaml | 493 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 474 insertions(+), 19 deletions(-) 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 From 2bd0bb09902627fc7b8da8ccb48f63ea0334c756 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 18 Feb 2026 11:25:23 +0000 Subject: [PATCH 2/5] add initial i15-1 app --- apps/i15-1/.gitignore | 24 ++ apps/i15-1/helm/Chart.yaml | 11 + apps/i15-1/helm/values.yaml | 17 + apps/i15-1/index.html | 13 + apps/i15-1/package.json | 32 ++ apps/i15-1/public/diamond.svg | 11 + apps/i15-1/public/mockServiceWorker.js | 343 ++++++++++++++++++ .../InstrumentSessionView.tsx | 93 +++++ apps/i15-1/src/components/WaffleNavbar.tsx | 42 +++ .../InstrumentSessionContext.ts | 10 + .../InstrumentSessionProvider.tsx | 28 ++ .../instrumentSession/useInstrumentSession.ts | 12 + apps/i15-1/src/main.tsx | 48 +++ apps/i15-1/src/mocks/browser.ts | 4 + apps/i15-1/src/mocks/handlers.ts | 21 ++ apps/i15-1/src/routes/Dashboard.tsx | 41 +++ apps/i15-1/src/routes/Layout.tsx | 15 + apps/i15-1/src/routes/Robot.tsx | 17 + apps/i15-1/src/style.css | 96 +++++ apps/i15-1/tsconfig.json | 7 + apps/i15-1/vite.config.ts | 10 + 21 files changed, 895 insertions(+) create mode 100644 apps/i15-1/.gitignore create mode 100644 apps/i15-1/helm/Chart.yaml create mode 100644 apps/i15-1/helm/values.yaml create mode 100644 apps/i15-1/index.html create mode 100644 apps/i15-1/package.json create mode 100644 apps/i15-1/public/diamond.svg create mode 100644 apps/i15-1/public/mockServiceWorker.js create mode 100644 apps/i15-1/src/components/InstrumentSessionSelection/InstrumentSessionView.tsx create mode 100644 apps/i15-1/src/components/WaffleNavbar.tsx create mode 100644 apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts create mode 100644 apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx create mode 100644 apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts create mode 100644 apps/i15-1/src/main.tsx create mode 100644 apps/i15-1/src/mocks/browser.ts create mode 100644 apps/i15-1/src/mocks/handlers.ts create mode 100644 apps/i15-1/src/routes/Dashboard.tsx create mode 100644 apps/i15-1/src/routes/Layout.tsx create mode 100644 apps/i15-1/src/routes/Robot.tsx create mode 100644 apps/i15-1/src/style.css create mode 100644 apps/i15-1/tsconfig.json create mode 100644 apps/i15-1/vite.config.ts 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 ( +
+ + + + + + + e.stopPropagation()}> + { + setInstrumentSession(visitToText(visit)); + setAnchorEl(null); + }} + /> + + + {sessionsList.map((option, index) => ( + handleMenuItemClick(event, index)} + > + {option} + + ))} + +
+ ); +} + +export default InstrumentSessionView; 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..41d5f08 --- /dev/null +++ b/apps/i15-1/src/main.tsx @@ -0,0 +1,48 @@ +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 { 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: , + }, + ], + }, +]); + +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 + + + + + + + + + ); +} + +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..8d4a63e --- /dev/null +++ b/apps/i15-1/src/routes/Robot.tsx @@ -0,0 +1,17 @@ +import { Box, Typography, Stack } from "@mui/material"; + +function Robot() { + return ( + <> + + + + Robot! + + + + + ); +} + +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/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: {}, + }, +}); From 2cf9b55c9ba1bc47ea391021670499cda05d6b94 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 18 Feb 2026 13:39:09 +0000 Subject: [PATCH 3/5] add robot path --- apps/i15-1/src/main.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/i15-1/src/main.tsx b/apps/i15-1/src/main.tsx index 41d5f08..589ee81 100644 --- a/apps/i15-1/src/main.tsx +++ b/apps/i15-1/src/main.tsx @@ -5,6 +5,7 @@ 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 { @@ -29,6 +30,10 @@ const router = createBrowserRouter([ index: true, element: , }, + { + path: "robot", + element: , + }, ], }, ]); From 7d3afec72bfe5c756235d31076ab5dc33d40a1a1 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 18 Feb 2026 14:58:27 +0000 Subject: [PATCH 4/5] add load and unload buttons to the robot page --- apps/i15-1/src/main.tsx | 2 -- apps/i15-1/src/routes/Robot.tsx | 62 +++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/apps/i15-1/src/main.tsx b/apps/i15-1/src/main.tsx index 589ee81..dbdead3 100644 --- a/apps/i15-1/src/main.tsx +++ b/apps/i15-1/src/main.tsx @@ -40,7 +40,6 @@ const router = createBrowserRouter([ enableMocking().then(() => { createRoot(document.getElementById("root")!).render( - // @@ -48,6 +47,5 @@ enableMocking().then(() => { , - // , ); }); diff --git a/apps/i15-1/src/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 8d4a63e..76d144a 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -1,13 +1,69 @@ +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: number; + pin_number: number; +}; function Robot() { + const { instrumentSession, setInstrumentSession } = useInstrumentSession(); + const [formData, setFormData] = useState({ + puck_number: 1, + pin_number: 1, + }); return ( <> - - - Robot! + + + Sample Position + + { + setFormData({ ...formData, ["puck_number"]: parsedValue }); + }} + /> + { + setFormData({ ...formData, ["pin_number"]: parsedValue }); + }} + /> + + + From a9c149e01232f21fafe466c2ea05f88fa3f57dbc Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 18 Feb 2026 15:19:35 +0000 Subject: [PATCH 5/5] change robot load plan name --- apps/i15-1/src/components/NumberInput.tsx | 124 ++++++++++++++++++++ apps/i15-1/src/components/RunPlanButton.tsx | 41 +++++++ apps/i15-1/src/routes/Robot.tsx | 22 ++-- apps/i15-1/src/utils/api.ts | 86 ++++++++++++++ 4 files changed, 262 insertions(+), 11 deletions(-) create mode 100644 apps/i15-1/src/components/NumberInput.tsx create mode 100644 apps/i15-1/src/components/RunPlanButton.tsx create mode 100644 apps/i15-1/src/utils/api.ts 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/routes/Robot.tsx b/apps/i15-1/src/routes/Robot.tsx index 76d144a..0ceef1a 100644 --- a/apps/i15-1/src/routes/Robot.tsx +++ b/apps/i15-1/src/routes/Robot.tsx @@ -5,15 +5,15 @@ import { NumberInput } from "../components/NumberInput"; import RunPlanButton from "../components/RunPlanButton"; type RobotSampleFormData = { - puck_number: number; - pin_number: number; + puck: number; + position: number; }; function Robot() { const { instrumentSession, setInstrumentSession } = useInstrumentSession(); const [formData, setFormData] = useState({ - puck_number: 1, - pin_number: 1, + puck: 1, + position: 1, }); return ( <> @@ -36,24 +36,24 @@ function Robot() { }} > { - setFormData({ ...formData, ["puck_number"]: parsedValue }); + setFormData({ ...formData, ["puck"]: parsedValue }); }} /> { - setFormData({ ...formData, ["pin_number"]: parsedValue }); + setFormData({ ...formData, ["position"]: parsedValue }); }} /> { + 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(); +}