Note: For general setup, prerequisites, and root-level commands, see the main README.
This directory contains the frontend workspace for the Hyperloop Control Station, built with React, TypeScript, and Vite.
The frontend is organized as 6 workspaces out of 9 in the whole monorepo, divided into 3 main areas:
| Workspace | Description |
|---|---|
testing-view |
Primary telemetry testing and debugging interface |
competition-view |
Competition-focused UI (simplified view) |
frontend-kit/ui or @workspace/ui |
Component library built on shadcn/ui and Radix UI |
frontend-kit/core or @workspace/core |
Shared business logic, WebSocket utilities, and types |
frontend-kit/eslint-config or @workspace/eslint-config |
Common ESLint configurations |
frontend-kit/typescript-config or @workspace/typescript-config |
Common TypeScript configurations |
- React 19 with TypeScript
- Vite for build tooling
- Zustand for state management
- React Router for navigation
- Radix UI / shadcn/ui for UI components
- RxJS for reactive data streams
- WebSocket for real-time backend communication
- @dnd-kit for drag-and-drop functionality
The application uses Zustand with a slice-based architecture, organized by feature domain:
appSlice- Application mode, settings, and configurationconnectionsSlice- WebSockets connection statusestelemetrySlice- Real-time telemetry data buffermessagesSlice- System messages and logscatalogSlice- Static definitions for telemetry packets and commands
- Workspace Feature (
features/workspace)workspacesSlice- Manages workspace layoutrightSidebarSlice- UI state for the collapsible sidebar and its tabs
- Charts Feature (
features/charts)chartsSlice- Manages chart instances, series configuration, and visualization settings
- Filtering Feature (
features/filtering)filteringSlice- Manages active filters, search queries, and category selection
Testing View supports multiple workspaces to organize different testing scenarios. Each workspace has:
- Independent filters for commands and telemetry
- Separate chart configurations
- Isolated tab state and expanded items
- Persistent configuration
The frontend connects to the Go backend via WebSocket for real-time communication:
- Connection:
useWebSockethook from@workspace/core - Topics: Subscribe to specific data streams using
useTopicpodData/update- Telemetry dataconnection/update- Backend connection statusmessage/update- System messages
- Sending packets: Send a message through websocket using
postmethod fromsocketService
Example:
import { useTopic, useWebSocket } from "@workspace/ui/hooks";
import { socketService } from "@workspace/core";
const { isConnected } = useWebSocket();
useTopic<TelemetryData>("podData/update", (data) => {
// Handle telemetry data
});
socketService.post("order/send", <payload>);The shared UI package provides:
- shadcn/ui components (Button, Dialog, Dropdown, etc.)
- Custom components (Sidebar, Charts, Filters)
- Hooks (useWebSocket, useTopic, useLogger)
- Icons from Lucide React
Import components from @workspace/ui:
import { Button, Dialog } from "@workspace/ui";
import { Plus, Settings } from "@workspace/ui/icons";- CSS Variables for theming (defined in
globals.css) - Tailwind CSS for utility classes
- Dark mode support via CSS class toggling
- Multiple color schemes (default and pink)
To add a new Lucide icon to the shared UI library:
- Look up the icon on Lucide Icons
- Find the first category listed for that icon
- Add the import to the corresponding category file in
frontend-kit/ui/src/icons/ - If the category file doesn't exist, create it and add its export to
index.ts - Keep the same alphabetical order as the icon categories
Example: The Axe icon's first category is Tools, so you would add its import to tools.ts:
// frontend-kit/ui/src/icons/tools.ts
export { Axe, Hammer } from "lucide-react";frontend/
├── frontend-kit/
│ ├── ui/ # Shared UI components
│ │ ├── src/components/ # React components
│ │ ├── src/hooks/ # Custom hooks
│ │ └── src/styles/ # Global styles
│ ├── core/ # Business logic
│ │ └── src/ # WebSocket, utilities, types
│ ├── eslint-config/ # ESLint configs
│ └── typescript-config/ # TS configs
├── testing-view/
│ ├── src/
│ │ ├── assets/ # Assets (images, gifs, etc.)
│ │ ├── components/ # Global UI components
│ │ ├── features/ # Components, hooks, types and store slices related to features
│ │ ├── layout/ # App layout
│ │ ├── pages/ # Route pages
│ │ ├── store/ # Global Zustand store slices
│ │ ├── hooks/ # Global custom hooks
│ │ ├── constants/ # Config and constants
│ │ ├── types/ # Global TypeScript types
│ │ ├── mocks/ # Mocks
│ │ └── lib/ # Utilities
│ └── public/ # Static assets
└── competition-view/
└── src/ # Similar structure
Use pnpm's --filter flag (run from root or frontend directory):
pnpm add <package> --filter testing-viewFrom root:
pnpm dev --filter testing-viewpnpm lint
pnpm format- Test framework: Vitest
- Component testing: @testing-library/react
Run tests:
pnpm testBuild all frontend packages:
pnpm buildPreview production builds:
pnpm preview- Ensure backend is running on the expected port
- Check
.env.developmentforVITE_BACKEND_URLconfiguration
- Use Redux DevTools extension for debugging Zustand state
- Ensure getters return stable references (use constants for empty arrays/objects)
- Use React DevTools Profiler to identify excessive re-renders
- Ensure Zustand selectors are properly scoped