From 0d8de6d85b3fedc86fa85ec6533821ad56c11061 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Mon, 20 Jul 2026 16:17:57 +0000 Subject: [PATCH 1/9] add instrumentSession context and view component --- .../InstrumentSessionContext.ts | 10 ++ .../InstrumentSessionProvider.tsx | 28 ++++++ .../InstrumentSessionView.tsx | 93 +++++++++++++++++++ .../instrumentSession/useInstrumentSession.ts | 12 +++ 4 files changed, 143 insertions(+) create mode 100644 packages/app-shell/src/context/instrumentSession/InstrumentSessionContext.ts create mode 100644 packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx create mode 100644 packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx create mode 100644 packages/app-shell/src/context/instrumentSession/useInstrumentSession.ts diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionContext.ts b/packages/app-shell/src/context/instrumentSession/InstrumentSessionContext.ts new file mode 100644 index 00000000..74a9c936 --- /dev/null +++ b/packages/app-shell/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/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx new file mode 100644 index 00000000..cf91adcb --- /dev/null +++ b/packages/app-shell/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/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx new file mode 100644 index 00000000..0fd8a4d9 --- /dev/null +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -0,0 +1,93 @@ +import { useState } from "react"; +import { visitToText, VisitInput } from "@diamondlightsource/sci-react-ui"; +import { useInstrumentSession } from "./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/packages/app-shell/src/context/instrumentSession/useInstrumentSession.ts b/packages/app-shell/src/context/instrumentSession/useInstrumentSession.ts new file mode 100644 index 00000000..2b19f863 --- /dev/null +++ b/packages/app-shell/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; +}; From 247078ba465c2b3fdfc83d25380be0bbc1c3a84d Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Tue, 21 Jul 2026 13:55:16 +0000 Subject: [PATCH 2/9] re-organise, remove from i15-1 --- apps/i15-1/src/AppProviders.tsx | 2 +- .../InstrumentSessionContext.ts | 10 ------- .../InstrumentSessionProvider.tsx | 28 ------------------- .../instrumentSession/useInstrumentSession.ts | 12 -------- apps/i15-1/src/routes/Dashboard.tsx | 6 ++-- packages/app-shell/src/TopBar.tsx | 8 ++++++ .../InstrumentSessionContext.ts | 10 ------- .../InstrumentSessionProvider.tsx | 24 ++++++++++++++-- .../InstrumentSessionView.tsx | 10 +++++-- .../instrumentSession/useInstrumentSession.ts | 12 -------- packages/app-shell/src/index.tsx | 2 ++ 11 files changed, 43 insertions(+), 81 deletions(-) delete mode 100644 apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts delete mode 100644 apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx delete mode 100644 apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts delete mode 100644 packages/app-shell/src/context/instrumentSession/InstrumentSessionContext.ts delete mode 100644 packages/app-shell/src/context/instrumentSession/useInstrumentSession.ts diff --git a/apps/i15-1/src/AppProviders.tsx b/apps/i15-1/src/AppProviders.tsx index c39801e5..8a41c13c 100644 --- a/apps/i15-1/src/AppProviders.tsx +++ b/apps/i15-1/src/AppProviders.tsx @@ -2,7 +2,7 @@ import type { Api } from "@atlas/blueapi"; import { ThemeProvider } from "@diamondlightsource/sci-react-ui"; import type { Theme } from "@mui/material"; import type { ReactNode } from "react"; -import { InstrumentSessionProvider } from "./context/instrumentSession/InstrumentSessionProvider"; +import { InstrumentSessionProvider } from "@atlas/app-shell"; import { Provider as ReduxProvider } from "react-redux"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { BlueapiProvider } from "@atlas/blueapi-query"; diff --git a/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts b/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts deleted file mode 100644 index 74a9c936..00000000 --- a/apps/i15-1/src/context/instrumentSession/InstrumentSessionContext.ts +++ /dev/null @@ -1,10 +0,0 @@ -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 deleted file mode 100644 index cf91adcb..00000000 --- a/apps/i15-1/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ /dev/null @@ -1,28 +0,0 @@ -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 deleted file mode 100644 index 2b19f863..00000000 --- a/apps/i15-1/src/context/instrumentSession/useInstrumentSession.ts +++ /dev/null @@ -1,12 +0,0 @@ -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/routes/Dashboard.tsx b/apps/i15-1/src/routes/Dashboard.tsx index 4329a78f..6a6d6675 100644 --- a/apps/i15-1/src/routes/Dashboard.tsx +++ b/apps/i15-1/src/routes/Dashboard.tsx @@ -2,7 +2,7 @@ import { Container, Typography, Button, Stack } from "@mui/material"; import { Link } from "react-router-dom"; import PrecisionManufacturingIcon from "@mui/icons-material/PrecisionManufacturing"; import QueueIcon from "@mui/icons-material/Queue"; -import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView.tsx"; +// import InstrumentSessionView from "../components/InstrumentSessionSelection/InstrumentSessionView.tsx"; import { useUserAuth } from "../context/userAuth/useUserAuth.ts"; import { User } from "@diamondlightsource/sci-react-ui"; @@ -28,7 +28,7 @@ function Dashboard() { } colour="white" /> - + /> */} + {sessionsList.map((option, index) => ( + handleMenuItemClick(event, index)} + > + {option} + + ))} + e.stopPropagation()}> - - {sessionsList.map((option, index) => ( - handleMenuItemClick(event, index)} - > - {option} - - ))} ); } - -// export default InstrumentSessionView; From c2dcd5addc5917d2cfd77d3bd575a656861db342 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 22 Jul 2026 09:34:12 +0000 Subject: [PATCH 4/9] Add default list of sessions, update blueapi-ui to use global session --- apps/i15-1/src/AppProviders.tsx | 5 +- packages/app-shell/src/TopBar.tsx | 2 +- .../InstrumentSessionProvider.tsx | 17 +++++-- .../InstrumentSessionView.tsx | 24 ++++----- packages/app-shell/src/utils/common.ts | 14 ++++++ packages/blueapi-ui/package.json | 4 +- .../src/PlanBrowser/PlanParameters.tsx | 15 +----- pnpm-lock.yaml | 49 ++++++++++++++++--- 8 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 packages/app-shell/src/utils/common.ts diff --git a/apps/i15-1/src/AppProviders.tsx b/apps/i15-1/src/AppProviders.tsx index cb5f2e0d..cd1f96e0 100644 --- a/apps/i15-1/src/AppProviders.tsx +++ b/apps/i15-1/src/AppProviders.tsx @@ -22,7 +22,10 @@ export function AppProviders({ api, theme, children }: Props) { const config = useLoadPvwsConfig(); return ( - + diff --git a/packages/app-shell/src/TopBar.tsx b/packages/app-shell/src/TopBar.tsx index 978611b5..813a723a 100644 --- a/packages/app-shell/src/TopBar.tsx +++ b/packages/app-shell/src/TopBar.tsx @@ -78,7 +78,7 @@ export function TopBar({ title, open, setOpen }: Props) { mt: 1.25, }} > - + diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx index 225e9792..29e105d3 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -1,12 +1,13 @@ import { useState, useEffect, useContext, type ReactNode } from "react"; -// import { InstrumentSessionContext } from "./InstrumentSessionContext"; import { createContext } from "react"; -const STORAGE_KEY = "instrument-session-id"; +const ID_STORAGE_KEY = "instrument-session-id"; +const LIST_STORAGE_KEY = "instrument-session-list"; export type InstrumentSessionContextType = { instrumentSession: string; setInstrumentSession: (session: string) => void; + sessionsList: string[]; }; export const InstrumentSessionContext = createContext< @@ -16,21 +17,27 @@ export const InstrumentSessionContext = createContext< export const InstrumentSessionProvider = ({ children, defaultSessionId = "cm12345-1", + sessionsList = ["cm12345-2", "cm12345-2"], }: { children: ReactNode; defaultSessionId?: string; + sessionsList?: string[]; }) => { const [instrumentSession, setInstrumentSession] = useState(() => { - return localStorage.getItem(STORAGE_KEY) ?? defaultSessionId; + return localStorage.getItem(ID_STORAGE_KEY) ?? defaultSessionId; }); useEffect(() => { - localStorage.setItem(STORAGE_KEY, instrumentSession); + localStorage.setItem(ID_STORAGE_KEY, instrumentSession); }, [instrumentSession]); return ( {children} diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index ad709ff4..fb1ab0f2 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -1,18 +1,16 @@ import { useState } from "react"; import { visitToText, VisitInput } from "@diamondlightsource/sci-react-ui"; import { useInstrumentSession } from "./InstrumentSessionProvider"; +import { visitTextToVisit } from "../../utils/common"; import * as React from "react"; import Button from "@mui/material/Button"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Divider from "@mui/material/Divider"; -export function InstrumentSessionView({ - sessionsList, -}: { - sessionsList: string[]; -}) { - const { instrumentSession, setInstrumentSession } = useInstrumentSession(); +export function InstrumentSessionView() { + const { instrumentSession, setInstrumentSession, sessionsList } = + useInstrumentSession(); const [selectedIndex, setSelectedIndex] = useState(1); const id = React.useId(); @@ -43,7 +41,7 @@ export function InstrumentSessionView({ aria-haspopup="true" aria-expanded={open} onClick={handleClick} - color="secondary" // to let you actually see it + color="inherit" // to let you actually see it variant="text" > {instrumentSession} @@ -71,11 +69,13 @@ export function InstrumentSessionView({ e.stopPropagation()}> { setInstrumentSession(visitToText(visit)); setAnchorEl(null); diff --git a/packages/app-shell/src/utils/common.ts b/packages/app-shell/src/utils/common.ts new file mode 100644 index 00000000..e27649f9 --- /dev/null +++ b/packages/app-shell/src/utils/common.ts @@ -0,0 +1,14 @@ +import { type Visit, regexToVisit } from "@diamondlightsource/sci-react-ui"; + +const visitRegex = /^([a-z]{2})([1-9]\d*)-([1-9]\d*)/; +export const visitWithTemplateRegex = new RegExp(`${visitRegex.source}-(.+)$`); + +export function visitTextToVisit(visitid?: string): Visit | null { + if (visitid) { + const parsedVisit = visitRegex.exec(visitid); + if (parsedVisit != null) { + return regexToVisit(parsedVisit); + } + } + return null; +} diff --git a/packages/blueapi-ui/package.json b/packages/blueapi-ui/package.json index cba77235..30de97c7 100644 --- a/packages/blueapi-ui/package.json +++ b/packages/blueapi-ui/package.json @@ -11,6 +11,7 @@ "coverage": "vitest run --coverage" }, "dependencies": { + "@atlas/app-shell": "workspace:*", "@atlas/blueapi": "workspace:*", "@atlas/blueapi-query": "workspace:*", "@jsonforms/core": "3.6.0", @@ -18,7 +19,8 @@ "@jsonforms/react": "3.6.0", "@tanstack/react-query": "^5.90.21", "lucide-react": "^1.24.0", - "react-error-boundary": "^6.0.0" + "react-error-boundary": "^6.0.0", + "react-router-dom": "^7.18.1" }, "devDependencies": { "@atlas/vitest-conf": "workspace:*", diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx index a143f22d..28b02df2 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.tsx @@ -8,6 +8,7 @@ import { import { sanitisePlan, type SchemaNode } from "../utils/schema"; import type { Plan } from "@atlas/blueapi"; import { RunPlanButton } from "../RunPlanButton"; +import { useInstrumentSession } from "@atlas/app-shell"; import { ErrorBoundary } from "react-error-boundary"; @@ -33,9 +34,7 @@ export function PlanParameters({ plan }: { plan: Plan }) { const sanitisedPlan = sanitisePlan(plan); const [planParameters, setPlanParameters] = useState({}); - // TODO: Remove InstrumentSession box and state, retrieve from context when submitting. - // See https://github.com/DiamondLightSource/atlas/issues/57 - const [instrumentSession, setInstrumentSession] = useState("cm12345-1"); + const { instrumentSession } = useInstrumentSession(); return ( @@ -66,16 +65,6 @@ export function PlanParameters({ plan }: { plan: Plan }) { /> )} - {/* TODO: Remove InstrumentSession box and state, retrieve from context when submitting. - See https://github.com/DiamondLightSource/atlas/issues/57 */} - - setInstrumentSession(e.target.value)} - > - =18' react-dom: '>=18' + react-router-dom@7.18.1: + resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + react-router@7.13.1: resolution: {integrity: sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==} engines: {node: '>=20.0.0'} @@ -4997,6 +5010,16 @@ packages: react-dom: optional: true + react-router@7.18.1: + resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-select@5.10.2: resolution: {integrity: sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==} peerDependencies: @@ -6364,7 +6387,7 @@ snapshots: optionalDependencies: dayjs: 1.10.7 - '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: '@mui/icons-material': 7.3.11(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@reduxjs/toolkit': 2.12.0(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -6378,7 +6401,7 @@ snapshots: react-grid-layout: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-plotly.js: 2.6.0(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2(postcss@8.5.15)))(react@18.3.1) react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-toastify: 11.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) utf-8-validate: 5.0.10 uuid: 9.0.1 @@ -6387,7 +6410,7 @@ snapshots: - graphql - plotly.js - '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + '@diamondlightsource/cs-web-lib@0.10.15(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(graphql@16.14.2)(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react-dom@18.3.1(react@18.3.1))(react-gauge-component@2.0.29(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': dependencies: '@mui/icons-material': 7.3.11(@mui/material@6.5.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1) '@reduxjs/toolkit': 2.12.0(react-redux@7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -6401,7 +6424,7 @@ snapshots: react-grid-layout: 2.2.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-plotly.js: 2.6.0(plotly.js@2.35.3(mapbox-gl@1.13.3)(webpack@5.106.2))(react@18.3.1) react-redux: 7.2.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-toastify: 11.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) utf-8-validate: 5.0.10 uuid: 9.0.1 @@ -11312,6 +11335,12 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-router: 7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router-dom@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-router@7.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: cookie: 1.1.1 @@ -11320,6 +11349,14 @@ snapshots: optionalDependencies: react-dom: 18.3.1(react@18.3.1) + react-router@7.18.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.1.1 + react: 18.3.1 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-select@5.10.2(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.28.6 From b823a351aa17b9d122de1c81160ccb1ac1b940b5 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Wed, 22 Jul 2026 13:49:41 +0000 Subject: [PATCH 5/9] Update current tests --- packages/app-shell/src/Layout.tsx | 8 ++++++++ packages/app-shell/src/TopBar.test.tsx | 8 ++++++++ .../InstrumentSessionProvider.tsx | 1 - .../src/PlanBrowser/PlanParameters.test.tsx | 14 +++++++++++++- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/app-shell/src/Layout.tsx b/packages/app-shell/src/Layout.tsx index 3db097f1..7fdf999a 100644 --- a/packages/app-shell/src/Layout.tsx +++ b/packages/app-shell/src/Layout.tsx @@ -6,6 +6,14 @@ import type { RouterProps } from "./Router"; import { usePersistentDrawerState } from "./usePersistentDrawerState"; import { Toolbar } from "@mui/material"; +// mock instrument session view which is out of scope of this test +export function InstrumentSessionView() { + return ; +} +vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ + InstrumentSessionView: InstrumentSessionView, +})); + export function Layout(props: RouterProps) { const { open, setOpen } = usePersistentDrawerState(); diff --git a/packages/app-shell/src/TopBar.test.tsx b/packages/app-shell/src/TopBar.test.tsx index 52a3c47d..9e31c941 100644 --- a/packages/app-shell/src/TopBar.test.tsx +++ b/packages/app-shell/src/TopBar.test.tsx @@ -10,6 +10,14 @@ vi.mock("@diamondlightsource/sci-react-ui", async () => { }; }); +// mock instrument session view which is out of scope of this test +export function InstrumentSessionView() { + return ; +} +vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ + InstrumentSessionView: InstrumentSessionView, +})); + describe("TopBar", () => { const barProps = { title: "Test application", diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx index 29e105d3..9d3990ab 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -2,7 +2,6 @@ import { useState, useEffect, useContext, type ReactNode } from "react"; import { createContext } from "react"; const ID_STORAGE_KEY = "instrument-session-id"; -const LIST_STORAGE_KEY = "instrument-session-list"; export type InstrumentSessionContextType = { instrumentSession: string; diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx index 8dd2c1ea..4e05e74d 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx @@ -2,6 +2,11 @@ import type { Plan } from "@atlas/blueapi"; import { render, screen } from "@atlas/vitest-conf"; import { PlanParameters } from "./PlanParameters"; +import { + useInstrumentSession, + type InstrumentSessionContextType, +} from "@atlas/app-shell"; +import { useState } from "react"; const mockJsonFormsImpl = vi.fn(() => { return
; @@ -21,6 +26,14 @@ vi.mock("../RunPlanButton", () => ({ RunPlanButton: RunPlanButton, })); +// mock use instrument session +const mockUseIS = vi.fn(() => ({ + instrumentSession: "cm12345-1", +})); +vi.mock("@atlas/app-shell", () => ({ + useInstrumentSession: () => mockUseIS(), +})); + const plan: Plan = { name: "hi_plan", description: "Says hi to you", @@ -38,7 +51,6 @@ describe("PlanParameters", () => { expect(screen.getByText(plan.name)).toBeInTheDocument(); expect(screen.getByText(plan.description!)).toBeInTheDocument(); expect(screen.getByTestId("jsonforms-sentinel")).toBeInTheDocument(); - expect(screen.getByRole("textbox", { name: "Instrument Session" })); expect(screen.getByRole("button", { name: "Run" })); }); From 02b59a080bde8ec6c07a225fdbf2a7b96b207c8e Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Mon, 27 Jul 2026 10:26:58 +0000 Subject: [PATCH 6/9] tidy up and start on tests --- packages/app-shell/src/TopBar.tsx | 2 +- .../InstrumentSessionProvider.tsx | 2 +- .../InstrumentSessionView.test.tsx | 47 +++++++++++++++++++ .../src/PlanBrowser/PlanParameters.test.tsx | 5 -- 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx diff --git a/packages/app-shell/src/TopBar.tsx b/packages/app-shell/src/TopBar.tsx index 813a723a..3b9355bc 100644 --- a/packages/app-shell/src/TopBar.tsx +++ b/packages/app-shell/src/TopBar.tsx @@ -78,7 +78,7 @@ export function TopBar({ title, open, setOpen }: Props) { mt: 1.25, }} > - + diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx index 9d3990ab..4951a618 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionProvider.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useContext, type ReactNode } from "react"; import { createContext } from "react"; -const ID_STORAGE_KEY = "instrument-session-id"; +export const ID_STORAGE_KEY = "instrument-session-id"; export type InstrumentSessionContextType = { instrumentSession: string; diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx new file mode 100644 index 00000000..d9a87102 --- /dev/null +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -0,0 +1,47 @@ +import { render, screen } from "@atlas/vitest-conf"; +import { InstrumentSessionView } from "./InstrumentSessionView"; +import { + InstrumentSessionProvider, + useInstrumentSession, +} from "./InstrumentSessionProvider"; +import { useState, type ReactNode } from "react"; +import { ID_STORAGE_KEY } from "./InstrumentSessionProvider"; + +function renderComponentWithProvider(children: ReactNode) { + return {children}; +} + +// mock use instrument session +const mockIS = ({return localStorage.getItem(ID_STORAGE_KEY) +}); +const setMockIS = (instrumentSession: string) => { + localStorage.setItem(ID_STORAGE_KEY, instrumentSession); +}; +const mockUseIS = () => ({ + instrumentSession: mockIS, + setInstrumentSession: setMockIS, + sessionsList: ["cm123-4", "cm567-8"], +}); +vi.mock("./InstrumentSessionProvider", () => ({ + useInstrumentSession: () => mockUseIS(), + ID_STORAGE_KEY: "instrument-session-id", +})); + +describe("InstrumentSessionView", () => { + beforeEach(() => { + localStorage.clear(); + }); + + it("reads instrument session from localStorage", () => { + localStorage.setItem(ID_STORAGE_KEY, "cm12345-1"); + const { instrumentSession, setInstrumentSession, sessionsList } = + useInstrumentSession(); + expect(instrumentSession).toBe("cm12345-1"); + }); + + // it("renders the instrument session provided", () => { + // localStorage.setItem(ID_STORAGE_KEY, "cm12345-1"); + // renderComponentWithProvider(); + // expect(screen.getAllByText("cm12345-1")); + // }); +}); diff --git a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx index 4e05e74d..2604510c 100644 --- a/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx +++ b/packages/blueapi-ui/src/PlanBrowser/PlanParameters.test.tsx @@ -2,11 +2,6 @@ import type { Plan } from "@atlas/blueapi"; import { render, screen } from "@atlas/vitest-conf"; import { PlanParameters } from "./PlanParameters"; -import { - useInstrumentSession, - type InstrumentSessionContextType, -} from "@atlas/app-shell"; -import { useState } from "react"; const mockJsonFormsImpl = vi.fn(() => { return
; From 6b11af0728f52ee6100d2494dddb5638c346c740 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Mon, 27 Jul 2026 13:40:09 +0000 Subject: [PATCH 7/9] fix tests --- .../InstrumentSessionView.test.tsx | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index d9a87102..0692e81d 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -1,47 +1,54 @@ -import { render, screen } from "@atlas/vitest-conf"; +import { render, screen, userEvent } from "@atlas/vitest-conf"; import { InstrumentSessionView } from "./InstrumentSessionView"; import { InstrumentSessionProvider, useInstrumentSession, } from "./InstrumentSessionProvider"; import { useState, type ReactNode } from "react"; -import { ID_STORAGE_KEY } from "./InstrumentSessionProvider"; -function renderComponentWithProvider(children: ReactNode) { - return {children}; +function renderComponentWithProvider() { + return render( + + + , + ); } // mock use instrument session -const mockIS = ({return localStorage.getItem(ID_STORAGE_KEY) +// const mockUseIS = vi.fn(() => ({ +// instrumentSession: "cm12345-1", +// setInstrumentSession: vi.fn(), +// sessionsList: ["cm123-4", "cm567-8"], +// })); +vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { + const actual = + await importOriginal(); + return { + ...actual, + useInstrumentSession: () => ({ + instrumentSession: "cm12345-1", + setInstrumentSession: vi.fn(), + sessionsList: ["cm123-4", "cm567-8"], + }), + }; }); -const setMockIS = (instrumentSession: string) => { - localStorage.setItem(ID_STORAGE_KEY, instrumentSession); -}; -const mockUseIS = () => ({ - instrumentSession: mockIS, - setInstrumentSession: setMockIS, - sessionsList: ["cm123-4", "cm567-8"], -}); -vi.mock("./InstrumentSessionProvider", () => ({ - useInstrumentSession: () => mockUseIS(), - ID_STORAGE_KEY: "instrument-session-id", -})); describe("InstrumentSessionView", () => { beforeEach(() => { localStorage.clear(); }); - it("reads instrument session from localStorage", () => { - localStorage.setItem(ID_STORAGE_KEY, "cm12345-1"); - const { instrumentSession, setInstrumentSession, sessionsList } = - useInstrumentSession(); - expect(instrumentSession).toBe("cm12345-1"); + it("shows current instrument session", () => { + renderComponentWithProvider(); + expect(screen.getByText("cm12345-1")).toBeInTheDocument(); }); - // it("renders the instrument session provided", () => { - // localStorage.setItem(ID_STORAGE_KEY, "cm12345-1"); - // renderComponentWithProvider(); - // expect(screen.getAllByText("cm12345-1")); - // }); + it("shows sessions from the provided sessions list when clicked", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByText("cm12345-1")); + expect(screen.getByText("cm123-4")).toBeInTheDocument(); + expect(screen.getByText("cm567-8")).toBeInTheDocument(); + }); }); From a94f651183982b1ed70da27dffdc8ce7e2a1b568 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Mon, 27 Jul 2026 13:55:55 +0000 Subject: [PATCH 8/9] Tidy tests --- packages/app-shell/src/Layout.test.tsx | 8 ++++++++ packages/app-shell/src/Layout.tsx | 8 -------- .../InstrumentSessionView.test.tsx | 20 ++++++------------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/packages/app-shell/src/Layout.test.tsx b/packages/app-shell/src/Layout.test.tsx index 13b38f77..e07b3e1a 100644 --- a/packages/app-shell/src/Layout.test.tsx +++ b/packages/app-shell/src/Layout.test.tsx @@ -3,6 +3,14 @@ import type { RouterProps } from "./Router"; import { Layout } from "./Layout"; import { createMemoryRouter, RouterProvider } from "react-router-dom"; +// mock instrument session view which is out of scope of this test +export function InstrumentSessionView() { + return ; +} +vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ + InstrumentSessionView: InstrumentSessionView, +})); + describe("Layout", () => { it("shows title, nav section titles, and main content", () => { const props: RouterProps = { diff --git a/packages/app-shell/src/Layout.tsx b/packages/app-shell/src/Layout.tsx index 7fdf999a..3db097f1 100644 --- a/packages/app-shell/src/Layout.tsx +++ b/packages/app-shell/src/Layout.tsx @@ -6,14 +6,6 @@ import type { RouterProps } from "./Router"; import { usePersistentDrawerState } from "./usePersistentDrawerState"; import { Toolbar } from "@mui/material"; -// mock instrument session view which is out of scope of this test -export function InstrumentSessionView() { - return ; -} -vi.mock("./context/instrumentSession/InstrumentSessionView", () => ({ - InstrumentSessionView: InstrumentSessionView, -})); - export function Layout(props: RouterProps) { const { open, setOpen } = usePersistentDrawerState(); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index 0692e81d..f1614398 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -1,10 +1,7 @@ import { render, screen, userEvent } from "@atlas/vitest-conf"; import { InstrumentSessionView } from "./InstrumentSessionView"; -import { - InstrumentSessionProvider, - useInstrumentSession, -} from "./InstrumentSessionProvider"; -import { useState, type ReactNode } from "react"; +import { InstrumentSessionProvider } from "./InstrumentSessionProvider"; +import { describe, it, expect, vi } from "vitest"; function renderComponentWithProvider() { return render( @@ -14,19 +11,13 @@ function renderComponentWithProvider() { ); } -// mock use instrument session -// const mockUseIS = vi.fn(() => ({ -// instrumentSession: "cm12345-1", -// setInstrumentSession: vi.fn(), -// sessionsList: ["cm123-4", "cm567-8"], -// })); vi.mock(import("./InstrumentSessionProvider"), async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useInstrumentSession: () => ({ - instrumentSession: "cm12345-1", + instrumentSession: "cm54321-1", setInstrumentSession: vi.fn(), sessionsList: ["cm123-4", "cm567-8"], }), @@ -40,14 +31,15 @@ describe("InstrumentSessionView", () => { it("shows current instrument session", () => { renderComponentWithProvider(); - expect(screen.getByText("cm12345-1")).toBeInTheDocument(); + expect(screen.getByText("cm54321-1")).toBeInTheDocument(); }); it("shows sessions from the provided sessions list when clicked", async () => { renderComponentWithProvider(); const user = userEvent.setup(); - await user.click(screen.getByText("cm12345-1")); + await user.click(screen.getByText("cm54321-1")); + expect(screen.getByTestId("visit-field")).toBeInTheDocument(); expect(screen.getByText("cm123-4")).toBeInTheDocument(); expect(screen.getByText("cm567-8")).toBeInTheDocument(); }); From 4948c77bfe1ad5476bd67d7728b2ead57294b3a3 Mon Sep 17 00:00:00 2001 From: Emily Arnold Date: Mon, 27 Jul 2026 16:25:36 +0000 Subject: [PATCH 9/9] increase coverage --- .../InstrumentSessionView.test.tsx | 50 +++++++++++++++++-- .../InstrumentSessionView.tsx | 6 +-- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx index f1614398..776a27a5 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, userEvent } from "@atlas/vitest-conf"; +import { render, screen, userEvent, within } from "@atlas/vitest-conf"; import { InstrumentSessionView } from "./InstrumentSessionView"; import { InstrumentSessionProvider } from "./InstrumentSessionProvider"; import { describe, it, expect, vi } from "vitest"; @@ -29,18 +29,60 @@ describe("InstrumentSessionView", () => { localStorage.clear(); }); - it("shows current instrument session", () => { + it("shows only the current instrument session", () => { renderComponentWithProvider(); - expect(screen.getByText("cm54321-1")).toBeInTheDocument(); + expect(screen.getByTestId("session-input-button")).toBeInTheDocument(); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); }); it("shows sessions from the provided sessions list when clicked", async () => { renderComponentWithProvider(); const user = userEvent.setup(); - await user.click(screen.getByText("cm54321-1")); + await user.click(screen.getByTestId("session-input-button")); expect(screen.getByTestId("visit-field")).toBeInTheDocument(); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); expect(screen.getByText("cm123-4")).toBeInTheDocument(); expect(screen.getByText("cm567-8")).toBeInTheDocument(); }); + + it("closes menu when a menu item has been clicked", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + await user.click(screen.getByText("cm123-4")); + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("closes menu when button has been clicked again", async () => { + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + expect(screen.getByTestId("session-input-menu")).toBeInTheDocument(); + + const backdrop = screen.getByRole("presentation").firstChild; + if (backdrop instanceof Element) { + await user.click(backdrop); + } + expect(screen.queryByTestId("session-input-menu")).not.toBeInTheDocument(); + }); + + it("lets you input a valid session", async () => { + const onSubmit = vi.fn(); + renderComponentWithProvider(); + + const user = userEvent.setup(); + await user.click(screen.getByTestId("session-input-button")); + + const visitInput = screen.getByTestId("session-input-menu"); + const visitTextBox = within(visitInput).getByRole("textbox"); + await user.click(visitTextBox); + await user.clear(visitTextBox); + await user.type(visitTextBox, "cm1-1"); + await user.keyboard("{Enter}"); + }); }); diff --git a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx index fb1ab0f2..a5f26b50 100644 --- a/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx +++ b/packages/app-shell/src/context/instrumentSession/InstrumentSessionView.tsx @@ -13,7 +13,7 @@ export function InstrumentSessionView() { useInstrumentSession(); const [selectedIndex, setSelectedIndex] = useState(1); - const id = React.useId(); + const id = "session-input"; const buttonId = `${id}-button`; const menuId = `${id}-menu`; const [anchorEl, setAnchorEl] = React.useState(null); @@ -36,7 +36,7 @@ export function InstrumentSessionView() { return (