console-frontend-review

Depot Console Frontend Code Review Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "console-frontend-review" with this command: npx skills add ecto/muni/ecto-muni-console-frontend-review

Depot Console Frontend Code Review Skill

This skill provides comprehensive code review for the depot console React/TypeScript web application used for fleet operations and rover teleoperation.

Overview

The depot console is a React 19 web application providing real-time control and monitoring of BVR rovers. It features 3D visualization, WebSocket-based teleop, and 360° video streaming.

Technology Stack:

  • Framework: React 19 with Vite

  • Language: TypeScript (strict mode)

  • State: Zustand (single store)

  • Styling: Tailwind CSS v4

  • 3D: React Three Fiber + drei

  • UI Components: Radix UI primitives

  • Routing: React Router v7

  • Build: Vite with ESM

Architecture:

depot/console/ ├── src/ │ ├── main.tsx # Entry point │ ├── App.tsx # Router setup │ ├── store.ts # Zustand global state (single source of truth) │ ├── components/ # React components │ │ ├── scene/ # React Three Fiber 3D components │ │ ├── teleop/ # Teleoperation UI panels │ │ └── ui/ # Radix UI + CVA primitives │ ├── views/ # Page-level components │ ├── hooks/ # Custom React hooks │ │ ├── useRoverConnection.ts # WebSocket teleop │ │ ├── useVideoStream.ts # 360 video stream │ │ ├── useGamepad.ts # Gamepad input polling │ │ ├── useKeyboard.ts # Keyboard input handling │ │ └── useDiscovery.ts # Rover discovery service │ └── lib/ │ ├── types.ts # TypeScript type definitions │ ├── protocol.ts # Binary protocol codec │ └── utils.ts # Utility functions

Critical Files:

  • Store: depot/console/src/store.ts (~400 lines)

  • Types: depot/console/src/lib/types.ts (~300 lines)

  • Protocol: depot/console/src/lib/protocol.ts (~200 lines)

  • Rover connection: depot/console/src/hooks/useRoverConnection.ts (~250 lines)

  • Video stream: depot/console/src/hooks/useVideoStream.ts (~150 lines)

  • 3D scene: depot/console/src/components/scene/Scene.tsx (~200 lines)

State Management Review (Zustand)

Store Architecture

Location: depot/console/src/store.ts

Key Points to Review:

  • Single store created with create<ConsoleState>()

  • State organized into logical domains (fleet, connection, telemetry, input, camera, video)

  • Immutable updates (no direct state mutation)

  • Partial updates via set() with object spread

  • No Redux-style actions (direct setter methods)

Store Domains:

Fleet Management:

  • rovers: RoverInfo[]

  • List of discovered rovers

  • selectedRoverId: string | null

  • Currently selected rover

  • selectRover(id)

  • Select rover and update addresses

Connection State:

  • roverAddress: string

  • WebSocket teleop address (ws://localhost:4850)

  • videoAddress: string

  • WebSocket video address (ws://localhost:4851)

  • connected: boolean

  • Connection status

  • latencyMs: number

  • Round-trip latency

Telemetry (Real-time Rover State):

  • mode: Mode

  • Operational mode (Idle, Teleop, etc.)

  • pose: Pose

  • Position (x, y, theta)

  • velocity: Twist

  • Current velocity

  • power: PowerStatus

  • Battery voltage, current

  • temperatures: TempStatus

  • Motor and controller temps

Input:

  • input: GamepadInput

  • Normalized gamepad/keyboard input

  • inputSource: InputSource

  • "gamepad" | "keyboard" | "none"

Camera:

  • cameraMode: CameraMode

  • ThirdPerson, FirstPerson, FreeLook

  • cameraSettings

  • FOV, distance, offset

Video:

  • videoFrame: string | null

  • Blob URL for current frame

  • videoConnected: boolean

  • videoFps: number

Example Pattern:

// Good: Zustand store with domain slices export const useConsoleStore = create<ConsoleState>((set) => ({ // Fleet state rovers: [], selectedRoverId: null, selectRover: (id: string) => set((state) => { const rover = state.rovers.find((r) => r.id === id); return { selectedRoverId: id, roverAddress: rover ? ws://${rover.hostname}:4850 : state.roverAddress, videoAddress: rover ? ws://${rover.hostname}:4851 : state.videoAddress, }; }),

// Telemetry state mode: Mode.Disabled, pose: { x: 0, y: 0, theta: 0 }, updateTelemetry: (telemetry: Partial<Telemetry>) => set((state) => ({ ...state, ...telemetry, // Partial merge })),

// Connection state connected: false, setConnected: (connected: boolean) => set({ connected }), }));

Red Flags:

  • Direct state mutation (state.rovers.push(...) )

  • Missing immutability in updates

  • No TypeScript types for state shape

  • Large monolithic state (should be split into domains)

  • Computed values stored in state (should be derived)

See: Zustand Best Practices

State Consumption in Components

Key Points to Review:

  • Use useConsoleStore() hook to access state

  • Selector functions for performance (only re-render on relevant changes)

  • No prop drilling (state accessed directly from store)

Example Pattern:

// Good: Selector for specific state slice function TelemetryPanel() { const { mode, pose, velocity } = useConsoleStore((state) => ({ mode: state.mode, pose: state.pose, velocity: state.velocity, }));

return ( <div> <div>Mode: {ModeLabels[mode]}</div> <div>Position: ({pose.x.toFixed(2)}, {pose.y.toFixed(2)})</div> <div>Velocity: {velocity.linear.toFixed(2)} m/s</div> </div> ); }

// Bad: Selecting entire state (re-renders on any state change) const state = useConsoleStore(); // ❌ Don't do this

TypeScript Patterns Review

Type Definitions

Location: depot/console/src/lib/types.ts

Key Points to Review:

  • Enums defined with as const pattern

  • Type aliases derived from enum keys

  • Interfaces for object shapes (not types)

  • No any types (use unknown or proper types)

  • Strict null checks enabled

Example Pattern:

// Good: Const enum with type alias export const Mode = { Disabled: 0, Idle: 1, Teleop: 2, Autonomous: 3, EStop: 4, Fault: 5, } as const;

export type Mode = (typeof Mode)[keyof typeof Mode];

// Good: Label map for UI display export const ModeLabels: Record<Mode, string> = { [Mode.Disabled]: "Disabled", [Mode.Idle]: "Idle", [Mode.Teleop]: "Teleop", [Mode.Autonomous]: "Autonomous", [Mode.EStop]: "E-Stop", [Mode.Fault]: "Fault", };

// Good: Interface for objects export interface Telemetry { mode: Mode; pose: Pose; velocity: Twist; power: PowerStatus; temperatures: TempStatus; }

// Good: Discriminated union for input source export type InputSource = "gamepad" | "keyboard" | "none";

Red Flags:

  • String enums instead of numeric (breaks binary protocol)

  • type used for objects (use interface )

  • Missing null checks

  • any types

  • Duplicate type definitions

Component Props

Key Points to Review:

  • Props interfaces defined with interface keyword

  • Optional props use ? operator

  • Destructured in function parameters

  • Children typed with React.ReactNode

Example Pattern:

// Good: Props interface interface TelemetryPanelProps { className?: string; showAdvanced?: boolean; }

export function TelemetryPanel({ className, showAdvanced = false }: TelemetryPanelProps) { // ... }

WebSocket Communication Review

Binary Protocol Implementation

Location: depot/console/src/lib/protocol.ts

Message Types:

  • Commands (Console → Rover): 0x01-0x0F

  • Telemetry (Rover → Console): 0x10-0x1F

  • Video (Rover → Console): 0x20-0x2F

Key Points to Review:

  • Binary encoding uses DataView with little-endian

  • Message type byte at offset 0

  • Payload follows type byte

  • Bounds checking before reading

  • No string encoding in critical path (use binary for performance)

Command Encoding:

// Good: Binary command encoding export function encodeTwist(twist: Twist): ArrayBuffer { const buffer = new ArrayBuffer(25); // 1 + 3*8 bytes const view = new DataView(buffer);

view.setUint8(0, MSG_TWIST); // Message type view.setFloat64(1, twist.linear, true); // Little-endian f64 view.setFloat64(9, twist.angular, true); view.setUint8(17, twist.boost ? 1 : 0);

return buffer; }

// Bad: JSON encoding (too slow for 100Hz) const json = JSON.stringify({ type: "twist", ...twist }); // ❌ Inefficient

Telemetry Decoding:

// Good: Binary telemetry decoding with bounds check export function decodeTelemetry(data: ArrayBuffer): Telemetry { if (data.byteLength < 90) { throw new Error(Telemetry frame too short: ${data.byteLength} bytes); }

const view = new DataView(data); const type = view.getUint8(0);

if (type !== MSG_TELEMETRY) { throw new Error(Invalid message type: ${type}); }

return { mode: view.getUint8(1), pose: { x: view.getFloat64(2, true), y: view.getFloat64(10, true), theta: view.getFloat32(18, true), }, velocity: { linear: view.getFloat32(22, true), angular: view.getFloat32(26, true), boost: view.getUint8(30) !== 0, }, // ... more fields }; }

See: websocket-protocols.md for complete protocol reference.

WebSocket Connection Management

Location: depot/console/src/hooks/useRoverConnection.ts

Key Points to Review:

  • WebSocket created with new WebSocket(url)

  • Event listeners: onopen , onmessage , onclose , onerror

  • Binary type set to "arraybuffer"

  • Auto-reconnect with exponential backoff

  • Cleanup on unmount (close socket)

  • Error handling doesn't crash app

Example Pattern:

// Good: WebSocket with cleanup export function useRoverConnection() { const [ws, setWs] = useState<WebSocket | null>(null); const address = useConsoleStore((state) => state.roverAddress);

useEffect(() => { const socket = new WebSocket(address); socket.binaryType = "arraybuffer"; // Critical for binary protocol

socket.onopen = () => {
  console.log("Connected to rover");
  useConsoleStore.getState().setConnected(true);
};

socket.onmessage = (event: MessageEvent) => {
  const telemetry = decodeTelemetry(event.data);
  useConsoleStore.getState().updateTelemetry(telemetry);
};

socket.onclose = () => {
  console.log("Disconnected from rover");
  useConsoleStore.getState().setConnected(false);
  // Reconnect after 3s
  setTimeout(() => setWs(null), 3000);
};

socket.onerror = (error) => {
  console.error("WebSocket error:", error);
};

setWs(socket);

// Cleanup on unmount
return () => {
  socket.close();
};

}, [address]);

return { ws }; }

Red Flags:

  • No binaryType = "arraybuffer" (defaults to Blob, slower)

  • Missing cleanup (memory leak)

  • No reconnection logic

  • Errors thrown instead of logged

  • No timeout handling

Command Transmission

Key Points to Review:

  • Commands sent at appropriate rate (100Hz for twist)

  • Heartbeat sent periodically (10Hz)

  • No commands sent when disconnected

  • Binary encoding used (not JSON)

Example Pattern:

// Good: Command sending at 100Hz useEffect(() => { if (!ws || !connected) return;

const interval = setInterval(() => { const input = useConsoleStore.getState().input; const twist = { linear: input.linear, angular: input.angular, boost: input.boost }; const buffer = encodeTwist(twist); ws.send(buffer); }, 10); // 100Hz = 10ms period

return () => clearInterval(interval); }, [ws, connected]);

// Good: Heartbeat at 10Hz useEffect(() => { if (!ws || !connected) return;

const interval = setInterval(() => { const buffer = encodeHeartbeat(); ws.send(buffer); }, 100); // 10Hz = 100ms period

return () => clearInterval(interval); }, [ws, connected]);

Safety Features Review

Page Visibility Tracking

Purpose: Stop sending motor commands when tab loses focus (user switches tabs).

Key Points to Review:

  • document.visibilityState monitored

  • Commands stopped when hidden

  • Input cleared when tab not visible

  • Warning shown to user

Example Pattern:

// Good: Page visibility tracking useEffect(() => { const handleVisibilityChange = () => { if (document.hidden) { // Stop all commands useConsoleStore.getState().setInput({ linear: 0, angular: 0, boost: false, }); useConsoleStore.getState().setInputSource("none"); console.warn("Tab hidden, stopping commands"); } };

document.addEventListener("visibilitychange", handleVisibilityChange); return () => document.removeEventListener("visibilitychange", handleVisibilityChange); }, []);

Red Flags:

  • No visibility tracking (rover continues moving when tab hidden)

  • Commands sent regardless of focus state

E-Stop Button

Key Points to Review:

  • Prominent e-stop button in UI

  • Sends e-stop command immediately on click

  • Visual feedback (overlay, color change)

  • Keyboard shortcut (e.g., Space bar)

Example Pattern:

// Good: E-Stop button function EStopButton() { const { ws } = useRoverConnection();

const handleEStop = () => { if (!ws) return; const buffer = encodeEStop(); ws.send(buffer); // Visual feedback useConsoleStore.getState().addToast({ title: "E-Stop Activated", variant: "destructive", }); };

return ( <Button variant="destructive" size="lg" onClick={handleEStop} className="fixed top-4 right-4 z-50" > <AlertTriangle className="mr-2" /> E-STOP </Button> ); }

Input Handling Review

Gamepad Input

Location: depot/console/src/hooks/useGamepad.ts

Key Points to Review:

  • Polling via requestAnimationFrame (not event-based)

  • Dead zone applied (e.g., 0.1 threshold)

  • Axes normalized to [-1, 1]

  • Button state tracked

  • Cleanup on unmount

Example Pattern:

// Good: Gamepad polling with deadzone export function useGamepad() { const [input, setInput] = useState<GamepadInput>({ linear: 0, angular: 0, boost: false });

useEffect(() => { let frameId: number;

const poll = () => {
  const gamepads = navigator.getGamepads();
  const gamepad = gamepads[0];

  if (gamepad) {
    const DEADZONE = 0.1;

    // Left stick Y (inverted): linear
    let linear = -gamepad.axes[1];
    if (Math.abs(linear) &#x3C; DEADZONE) linear = 0;

    // Right stick X: angular
    let angular = gamepad.axes[2];
    if (Math.abs(angular) &#x3C; DEADZONE) angular = 0;

    // Button 0 (A): boost
    const boost = gamepad.buttons[0].pressed;

    setInput({ linear, angular, boost });
    useConsoleStore.getState().setInputSource("gamepad");
  }

  frameId = requestAnimationFrame(poll);
};

frameId = requestAnimationFrame(poll);
return () => cancelAnimationFrame(frameId);

}, []);

return input; }

Red Flags:

  • Event-based (gamepad API doesn't support events reliably)

  • No dead zone (jittery input)

  • Axes not normalized

  • Missing cleanup

Keyboard Input

Location: depot/console/src/hooks/useKeyboard.ts

Key Points to Review:

  • Global listeners on document or window

  • Key state tracked in Set or object

  • Input normalized to [-1, 1]

  • Cleanup removes listeners

  • Doesn't interfere with text inputs

Example Pattern:

// Good: Keyboard input with state tracking export function useKeyboard() { const [keys, setKeys] = useState<Set<string>>(new Set());

useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Don't capture if typing in input if (e.target instanceof HTMLInputElement) return;

  setKeys((prev) => new Set(prev).add(e.code));
};

const handleKeyUp = (e: KeyboardEvent) => {
  setKeys((prev) => {
    const next = new Set(prev);
    next.delete(e.code);
    return next;
  });
};

document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);

return () => {
  document.removeEventListener("keydown", handleKeyDown);
  document.removeEventListener("keyup", handleKeyUp);
};

}, []);

// Convert keys to input const input = useMemo(() => { let linear = 0; let angular = 0;

if (keys.has("KeyW")) linear += 1;
if (keys.has("KeyS")) linear -= 1;
if (keys.has("KeyA")) angular += 1;
if (keys.has("KeyD")) angular -= 1;

return { linear, angular, boost: keys.has("ShiftLeft") };

}, [keys]);

if (input.linear !== 0 || input.angular !== 0) { useConsoleStore.getState().setInputSource("keyboard"); }

return input; }

3D Visualization Review (React Three Fiber)

Scene Setup

Location: depot/console/src/components/scene/Scene.tsx

Key Points to Review:

  • <Canvas> wraps all Three.js components

  • Camera FOV reasonable (60-75°)

  • Lighting includes ambient + directional

  • Shadows enabled if needed

  • Performance monitoring (<Perf> in dev)

Example Pattern:

// Good: Canvas setup with lighting export function Scene() { return ( <Canvas shadows camera={{ fov: 60, position: [0, 5, 10] }}> <ambientLight intensity={0.5} /> <directionalLight position={[10, 10, 5]} castShadow /> <RoverModel /> <Ground /> <EquirectangularSky /> <CameraController /> </Canvas> ); }

Rover Model Animation

Key Points to Review:

  • Position interpolated with lerp (smooth motion)

  • Angle interpolation handles wraparound (0° ↔ 360°)

  • Delta time used for frame-rate independence

  • Model updates on telemetry change

Example Pattern:

// Good: Smooth position interpolation function RoverModel() { const pose = useConsoleStore((state) => state.pose); const ref = useRef<THREE.Group>(null);

useFrame((state, delta) => { if (!ref.current) return;

// Lerp position
ref.current.position.x = THREE.MathUtils.lerp(ref.current.position.x, pose.x, delta * 10);
ref.current.position.z = THREE.MathUtils.lerp(ref.current.position.z, -pose.y, delta * 10);

// Lerp rotation (handle wraparound)
const targetRot = -pose.theta;
const currentRot = ref.current.rotation.y;
const diff = ((targetRot - currentRot + Math.PI) % (2 * Math.PI)) - Math.PI;
ref.current.rotation.y += diff * delta * 10;

});

return ( <group ref={ref}> {/* Rover geometry */} </group> ); }

Red Flags:

  • Direct assignment (no interpolation, jumpy motion)

  • No wraparound handling for angles

  • Fixed delta (not frame-rate independent)

Memory Management

Key Points to Review:

  • Textures disposed on unmount

  • Geometries disposed when not needed

  • Materials disposed when not needed

  • Blob URLs revoked with URL.revokeObjectURL()

Example Pattern:

// Good: Texture cleanup useEffect(() => { if (!videoFrame) return;

const texture = new THREE.TextureLoader().load(videoFrame);

return () => { texture.dispose(); // Free GPU memory URL.revokeObjectURL(videoFrame); // Free blob URL }; }, [videoFrame]);

See: performance.md for optimization strategies.

Component Patterns Review

File Naming

Convention: PascalCase for components, camelCase for hooks/utils.

Key Points to Review:

  • Components: TelemetryPanel.tsx , RoverModel.tsx

  • Hooks: useGamepad.ts , useRoverConnection.ts

  • Utils: utils.ts , protocol.ts

  • All TypeScript (.ts or .tsx )

Component Structure

Key Points to Review:

  • Functional components (not class components)

  • Props destructured in parameters

  • Hooks at top of function (before any conditional)

  • Event handlers defined inside component

  • Return JSX with semantic HTML

Example Pattern:

// Good: Component structure interface TelemetryPanelProps { className?: string; }

export function TelemetryPanel({ className }: TelemetryPanelProps) { // 1. Zustand store access const { mode, pose, velocity } = useConsoleStore((state) => ({ mode: state.mode, pose: state.pose, velocity: state.velocity, }));

// 2. Local state const [expanded, setExpanded] = useState(false);

// 3. Effects useEffect(() => { // Side effects }, []);

// 4. Event handlers const handleToggle = () => setExpanded(!expanded);

// 5. Render return ( <Card className={cn("p-4", className)}> <h2>Telemetry</h2> <div>Mode: {ModeLabels[mode]}</div> <div>Position: ({pose.x.toFixed(2)}, {pose.y.toFixed(2)})</div> <Button onClick={handleToggle}>Toggle</Button> </Card> ); }

Testing and Linting

ESLint Configuration

Key Points to Review:

  • TypeScript ESLint rules enabled

  • React hooks rules enforced

  • No any types allowed

  • Unused vars detected

  • Import order enforced

Run linting:

npm run lint

Type Checking

Key Points to Review:

  • No TypeScript errors: npm run build

  • Strict mode enabled in tsconfig.json

  • All imports have types

Build Verification

Key Points to Review:

  • Vite build succeeds: npm run build

  • Bundle size reasonable (<1MB for main chunk)

  • No console errors in production build

References and Additional Resources

For more detailed information, see:

  • websocket-protocols.md - Binary protocol encoding/decoding

  • performance.md - React rendering optimization and memory management

  • CLAUDE.md - Project-wide conventions

Quick Review Commands

Lint code

npm run lint

Type check and build

npm run build

Dev server with hot-reload

npm run dev

Run tests (if configured)

npm run test

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

mcu-embedded-review

No summary provided by upstream source.

Repository SourceNeeds Review
General

firmware-review

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

documentation-automation

No summary provided by upstream source.

Repository SourceNeeds Review