From c6b1d668991bbfccdc260be83a1e89ceff7bb126 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 02:06:53 +0200 Subject: [PATCH 1/9] fix(competition-view): add full scren --- electron-app/src/app/initialization.js | 6 +++++ electron-app/src/menu/README.md | 33 ++++++++++---------------- electron-app/src/menu/menu.js | 26 +++++++++++++------- electron-app/src/windows/mainWindow.js | 5 ---- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/electron-app/src/app/initialization.js b/electron-app/src/app/initialization.js index 3e6195ca0..433c7fddc 100644 --- a/electron-app/src/app/initialization.js +++ b/electron-app/src/app/initialization.js @@ -3,19 +3,25 @@ * @description Application initialization: config setup and cleanup. */ +import { Menu } from "electron"; import { getConfigManager } from "../config/configInstance.js"; import { setupIpcHandlers } from "../ipc/handlers.js"; +import { createMenu } from "../menu/menu.js"; import { logger } from "../utils/logger.js"; import { cleanupLeftoverBackendProcesses } from "./cleanup.js"; /** * Initializes the application: + * - Sets the application menu shared by all windows * - Sets up IPC handlers * - Initializes configuration * - Cleans up leftover processes * @returns {Promise} */ async function initializeApp() { + // Application-wide menu so shortcuts (F11 fullscreen, F12 DevTools, ...) work in every window + Menu.setApplicationMenu(createMenu()); + // Setup IPC handlers for renderer process communication setupIpcHandlers(); diff --git a/electron-app/src/menu/README.md b/electron-app/src/menu/README.md index 8f3b95918..5484bfeb6 100644 --- a/electron-app/src/menu/README.md +++ b/electron-app/src/menu/README.md @@ -4,7 +4,7 @@ Application menu system for the Electron application. Provides the native menu b ## Overview -Creates and manages the native application menu bar with view switching, window controls, developer tools, and utility applications. +Creates the native application menu bar shared by all windows. Menu actions operate on the currently focused window, so shortcuts like fullscreen and DevTools work in the main window, the mode selector, and the log window alike. ## Files @@ -14,19 +14,14 @@ Creates and manages the native application menu bar with view switching, window ### File Menu -- **Reload** (`CmdOrCtrl+R`) - Reloads the current window +- **Reload** (`CmdOrCtrl+R`) - Reloads the focused window +- **Return to Selector** (`CmdOrCtrl+Shift+S`) - Stops services and returns to the mode selector - **Exit** (`CmdOrCtrl+Q`) - Quits the application -### View Menu +### Tools Menu -- **Control Station** (`CmdOrCtrl+1`) - Switches to Competition View -- **Ethernet View** (`CmdOrCtrl+2`) - Switches to Testing View -- **Toggle DevTools** (`F12`) - Opens/closes Chrome DevTools - -### Tools Menu (Developing) - -- **Start Packet Sender** - Launches packet sender utility (validates binary exists) -- **Stop Packet Sender** - Stops the running packet sender process +- **Toggle Full Screen** (`F11`) - Toggles fullscreen on the focused window +- **Toggle DevTools** (`F12`) - Opens/closes Chrome DevTools for the focused window ### Help Menu @@ -34,28 +29,24 @@ Creates and manages the native application menu bar with view switching, window ## Functions -### `createMenu(mainWindow)` +### `createMenu()` -Creates and sets the application menu bar. Takes the main window instance as parameter. +Builds and returns the application menu. Set it globally with `Menu.setApplicationMenu(createMenu())` so it applies to every window. ## Dependencies -- `../utils/paths.js` - For resolving binary paths -- `electron` - For Menu, dialog, and app APIs +- `electron` - For Menu, BrowserWindow, dialog, and app APIs ## Used By -- **`windows/mainWindow.js`** - Creates the menu when initializing the main window +- **`app/initialization.js`** - Sets the application-wide menu during app initialization ## Notes - Keyboard shortcuts automatically use `Cmd` on macOS and `Ctrl` on Windows/Linux - Menu appears in system menu bar on macOS, in window on Windows/Linux -- Packet sender binary existence is validated before starting -- View switching requires `loadView` to be imported from windows module +- Menu items resolve their target window at click time (focused window), so no window reference is needed when building the menu ## See Also -- [../windows/README.md](../windows/README.md) - Window management (used for view switching) -- [../processes/README.md](../processes/README.md) - Process management (packet sender) -- [../utils/README.md](../utils/README.md) - Utility functions (path resolution) +- [../windows/README.md](../windows/README.md) - Window management diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index a7cdc27e7..1ecf5b7c7 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -4,18 +4,19 @@ * Defines menu structure with File, Tools, and Help sections with keyboard shortcuts and actions. */ -import { Menu, app, dialog } from "electron"; +import { BrowserWindow, Menu, app, dialog } from "electron"; /** - * Creates and sets the application menu with File, Tools, and Help sections. - * Includes menu items for reloading, exiting, toggling DevTools, and app information. + * Creates the application menu with File, Tools, and Help sections. + * Includes menu items for reloading, exiting, toggling fullscreen/DevTools, and app information. + * Menu actions operate on the currently focused window, so the menu can be + * shared by all windows via Menu.setApplicationMenu. * View switching is no longer available since the mode is selected at startup. - * @param {import("electron").BrowserWindow} mainWindow - The main browser window instance to attach menu actions to. - * @returns {void} + * @returns {Menu} The built application menu. * @example - * createMenu(mainWindow); + * Menu.setApplicationMenu(createMenu()); */ -function createMenu(mainWindow) { +function createMenu() { const template = [ { label: "File", @@ -45,6 +46,15 @@ function createMenu(mainWindow) { { label: "Tools", submenu: [ + { + label: "Toggle Full Screen", + accelerator: "F11", + click: (_, browserWindow) => { + if (browserWindow) { + browserWindow.setFullScreen(!browserWindow.isFullScreen()); + } + }, + }, { label: "Toggle DevTools", accelerator: "F12", @@ -62,7 +72,7 @@ function createMenu(mainWindow) { { label: "About", click: () => { - dialog.showMessageBox(mainWindow, { + dialog.showMessageBox(BrowserWindow.getFocusedWindow() ?? undefined, { type: "info", title: "About", message: "Hyperloop UPV Control Station", diff --git a/electron-app/src/windows/mainWindow.js b/electron-app/src/windows/mainWindow.js index 0a963e325..b10f5a4cd 100644 --- a/electron-app/src/windows/mainWindow.js +++ b/electron-app/src/windows/mainWindow.js @@ -7,7 +7,6 @@ import { BrowserWindow, app, dialog } from "electron"; import fs from "fs"; import path from "path"; -import { createMenu } from "../menu/menu.js"; import { getAppPath } from "../utils/paths.js"; // Get the application root path @@ -57,10 +56,6 @@ function createWindow(screenWidth, screenHeight, initialView) { loadView(currentView); } - // Create application menu - const menu = createMenu(mainWindow); - mainWindow.setMenu(menu); - // Open DevTools in development mode (skip in test env to keep window order predictable) if (!app.isPackaged && process.env.NODE_ENV !== "test") { mainWindow.webContents.openDevTools(); From a0f0dfc59247f0fd2afd864016ff896e1088f692 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 02:17:00 +0200 Subject: [PATCH 2/9] fix(competition-view): zoom --- electron-app/src/menu/README.md | 3 +++ electron-app/src/menu/menu.js | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/electron-app/src/menu/README.md b/electron-app/src/menu/README.md index 5484bfeb6..9b124b27a 100644 --- a/electron-app/src/menu/README.md +++ b/electron-app/src/menu/README.md @@ -20,6 +20,9 @@ Creates the native application menu bar shared by all windows. Menu actions oper ### Tools Menu +- **Zoom In** (`CmdOrCtrl+Plus`) - Increases zoom of the focused window +- **Zoom Out** (`CmdOrCtrl+-`) - Decreases zoom of the focused window +- **Reset Zoom** (`CmdOrCtrl+0`) - Restores the focused window to 100% zoom - **Toggle Full Screen** (`F11`) - Toggles fullscreen on the focused window - **Toggle DevTools** (`F12`) - Opens/closes Chrome DevTools for the focused window diff --git a/electron-app/src/menu/menu.js b/electron-app/src/menu/menu.js index 1ecf5b7c7..f9cf5926f 100644 --- a/electron-app/src/menu/menu.js +++ b/electron-app/src/menu/menu.js @@ -46,6 +46,10 @@ function createMenu() { { label: "Tools", submenu: [ + { role: "zoomIn", label: "Zoom In" }, + { role: "zoomOut", label: "Zoom Out" }, + { role: "resetZoom", label: "Reset Zoom" }, + { type: "separator" }, { label: "Toggle Full Screen", accelerator: "F11", From 1874b7c95a7292d9023de490c69b414b8c8ce2ec Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 02:45:33 +0200 Subject: [PATCH 3/9] fix(competition-view): remove pressure --- frontend/competition-view/src/pages/Charts/Charts.tsx | 3 +-- frontend/competition-view/src/pages/Overview/Overview.tsx | 7 ------- .../src/pages/Overview/components/BoardsOverviewCard.tsx | 1 - 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/frontend/competition-view/src/pages/Charts/Charts.tsx b/frontend/competition-view/src/pages/Charts/Charts.tsx index 52b8d4282..58a6d6977 100644 --- a/frontend/competition-view/src/pages/Charts/Charts.tsx +++ b/frontend/competition-view/src/pages/Charts/Charts.tsx @@ -6,7 +6,7 @@ import { MoveVertical, Zap, } from "lucide-react"; -import { BOARDS, HVBMS, LCU, PCU, VCU } from "../../constants/measurements"; +import { BOARDS, HVBMS, LCU, PCU } from "../../constants/measurements"; import MultiSeriesChart, { type SeriesConfig } from "./components/MultiSeriesChart"; import TelemetryChart from "./components/TelemetryChart"; @@ -69,7 +69,6 @@ const Charts = () => ( {/* Row 3 — DLIM motor currents */} - {/* Row 4 — Airgaps */} diff --git a/frontend/competition-view/src/pages/Overview/Overview.tsx b/frontend/competition-view/src/pages/Overview/Overview.tsx index 2505a117b..48bf638c3 100644 --- a/frontend/competition-view/src/pages/Overview/Overview.tsx +++ b/frontend/competition-view/src/pages/Overview/Overview.tsx @@ -21,7 +21,6 @@ import { PACK_V_RANGE, PCU, PROP_CURRENT_RANGE, - VCU, } from "../../constants/measurements"; import useMeasurement from "../../hooks/useMeasurement"; import { useIsStale, useStaleFlags } from "../../hooks/useIsStale"; @@ -164,18 +163,12 @@ const BatteryCard = ({ title, icon: Icon, soc, socStale, rows }: BatteryCardProp const KinematicsCard = () => { const speed = useMeasurement(BOARDS.PCU, PCU.speed); const position = useMeasurement(BOARDS.PCU, PCU.position); - const highPsi = useMeasurement(BOARDS.VCU, VCU.highPressure); - const lowPsi = useMeasurement(BOARDS.VCU, VCU.lowPressure); const speedStale = useIsStale(BOARDS.PCU, PCU.speed); const posStale = useIsStale(BOARDS.PCU, PCU.position); - const highStale = useIsStale(BOARDS.VCU, VCU.highPressure); - const lowStale = useIsStale(BOARDS.VCU, VCU.lowPressure); const rows = [ { label: "Position", value: fmtNum(position), unit: "m", stale: posStale }, - { label: "High pres.", value: fmtNum(highPsi), unit: "bar", stale: highStale }, - { label: "Low pres.", value: fmtNum(lowPsi), unit: "bar", stale: lowStale }, ]; return ( diff --git a/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx b/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx index 3e54b53ad..61e71c31d 100644 --- a/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx +++ b/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx @@ -30,7 +30,6 @@ const ROWS: BoardRow[] = [ icon: Cpu, stateMeasurementKey: VCU.state, stats: [ - { label: "High pres.", measurementKey: VCU.highPressure, unit: "bar" }, { label: "SDC", measurementKey: VCU.sdcClosed, boolLabels: ["Closed", "Open"] }, { label: "Brakes", measurementKey: VCU.activeBrakes, boolLabels: ["Braked", "Unbraked"] }, ], From b1152cea699c609b1132a84be900adc7a495edd8 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 02:57:46 +0200 Subject: [PATCH 4/9] fix(compettiion-view): change break estatus --- .../src/components/header/DashboardStatusBar.tsx | 12 ++++++------ .../competition-view/src/constants/measurements.ts | 3 ++- .../competition-view/src/pages/Booster/Booster.tsx | 2 +- .../pages/Overview/components/BoardsOverviewCard.tsx | 2 +- .../src/pages/Overview/components/BrakeIndicator.tsx | 8 +++++--- .../pages/Overview/components/VehicleStateBanner.tsx | 4 ++-- 6 files changed, 17 insertions(+), 14 deletions(-) diff --git a/frontend/competition-view/src/components/header/DashboardStatusBar.tsx b/frontend/competition-view/src/components/header/DashboardStatusBar.tsx index 4bf6259d5..ab4b3f1e8 100644 --- a/frontend/competition-view/src/components/header/DashboardStatusBar.tsx +++ b/frontend/competition-view/src/components/header/DashboardStatusBar.tsx @@ -39,11 +39,11 @@ const StatValue = ({ const DashboardStatusBar = () => { const state = useMeasurement(BOARDS.VCU, VCU.state); - const brakeRaw = useMeasurement(BOARDS.VCU, VCU.activeBrakes); + const brakeRaw = useMeasurement(BOARDS.VCU, VCU.brakesStatus); const dcLinkV = useMeasurement(BOARDS.HVBMS, HVBMS.voltageReading) as number | undefined; const stateStale = useIsStale(BOARDS.VCU, VCU.state); - const brakeStale = useIsStale(BOARDS.VCU, VCU.activeBrakes); + const brakeStale = useIsStale(BOARDS.VCU, VCU.brakesStatus); const dcLinkStale = useIsStale(BOARDS.HVBMS, HVBMS.voltageReading); const dcLinkActive = dcLinkV !== undefined && dcLinkV > HVAL_THRESHOLD_V; @@ -53,11 +53,11 @@ const DashboardStatusBar = () => { ? "text-muted-foreground" : dcLinkActive ? "text-red-500" : "text-green-500"; - const brakeLabel = brakeRaw === undefined ? "—" : brakeRaw ? "BRAKED" : "UNBRAKED"; + const brakeLabel = brakeRaw === undefined ? "—" : String(brakeRaw); const brakeClass = - brakeStale ? STALE_TEXT_CLASS : - brakeRaw === true ? "text-red-500" : - brakeRaw === false ? "text-blue-500" : + brakeStale ? STALE_TEXT_CLASS : + brakeRaw === "BRAKED" ? "text-red-500" : + brakeRaw === "UNBRAKED" ? "text-blue-500" : "text-muted-foreground"; return ( diff --git a/frontend/competition-view/src/constants/measurements.ts b/frontend/competition-view/src/constants/measurements.ts index df6b35fa2..0d0e5951f 100644 --- a/frontend/competition-view/src/constants/measurements.ts +++ b/frontend/competition-view/src/constants/measurements.ts @@ -22,7 +22,8 @@ export const VCU = { lowPressure: "low_pressure", pressureRegulatorFdbk: "pressure_regulator_feedback", sdcClosed: "sdc_closed", - activeBrakes: "active_brakes", + // Enum: "BRAKED" | "UNBRAKED" + brakesStatus: "brakes_status", brakeFault: "brake_fault_detected", electrovalveEnabled: "electrovalve_enabled", // Sub-board connectivity as reported by the VCU diff --git a/frontend/competition-view/src/pages/Booster/Booster.tsx b/frontend/competition-view/src/pages/Booster/Booster.tsx index 2fdc737c7..ceea7ef48 100644 --- a/frontend/competition-view/src/pages/Booster/Booster.tsx +++ b/frontend/competition-view/src/pages/Booster/Booster.tsx @@ -101,7 +101,7 @@ const Booster = () => (
- + diff --git a/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx b/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx index 61e71c31d..9872b0840 100644 --- a/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx +++ b/frontend/competition-view/src/pages/Overview/components/BoardsOverviewCard.tsx @@ -31,7 +31,7 @@ const ROWS: BoardRow[] = [ stateMeasurementKey: VCU.state, stats: [ { label: "SDC", measurementKey: VCU.sdcClosed, boolLabels: ["Closed", "Open"] }, - { label: "Brakes", measurementKey: VCU.activeBrakes, boolLabels: ["Braked", "Unbraked"] }, + { label: "Brakes", measurementKey: VCU.brakesStatus }, ], }, { diff --git a/frontend/competition-view/src/pages/Overview/components/BrakeIndicator.tsx b/frontend/competition-view/src/pages/Overview/components/BrakeIndicator.tsx index 2077404d8..e2f64afcf 100644 --- a/frontend/competition-view/src/pages/Overview/components/BrakeIndicator.tsx +++ b/frontend/competition-view/src/pages/Overview/components/BrakeIndicator.tsx @@ -16,13 +16,15 @@ interface BrakeIndicatorProps { /** * Visual indicator that mirrors the control-station BrakeState widget. - * Reads VCU/active_brakes — true means brakes are engaged. + * Reads the VCU `brakes_status` enum ("BRAKED" | "UNBRAKED"). */ const BrakeIndicator = ({ compact = false }: BrakeIndicatorProps) => { - const raw = useMeasurement(BOARDS.VCU, VCU.activeBrakes); + const raw = useMeasurement(BOARDS.VCU, VCU.brakesStatus); const status: BrakeStatus = - raw === undefined ? "unknown" : raw ? "braked" : "unbraked"; + raw === "BRAKED" ? "braked" : + raw === "UNBRAKED" ? "unbraked" : + "unknown"; const { bg, text, label } = STATUS_STYLES[status]; diff --git a/frontend/competition-view/src/pages/Overview/components/VehicleStateBanner.tsx b/frontend/competition-view/src/pages/Overview/components/VehicleStateBanner.tsx index 1e02fa5e5..cd127111c 100644 --- a/frontend/competition-view/src/pages/Overview/components/VehicleStateBanner.tsx +++ b/frontend/competition-view/src/pages/Overview/components/VehicleStateBanner.tsx @@ -56,7 +56,7 @@ interface VehicleStateBannerProps { */ const VehicleStateBanner = ({ compact = false }: VehicleStateBannerProps) => { const state = useMeasurement(BOARDS.VCU, VCU.state); - const activeBrakes = useMeasurement(BOARDS.VCU, VCU.activeBrakes); + const brakesStatus = useMeasurement(BOARDS.VCU, VCU.brakesStatus); const category = categorise(state); const { banner, valueText, badgeClass } = STATE_STYLES[category]; @@ -80,7 +80,7 @@ const VehicleStateBanner = ({ compact = false }: VehicleStateBannerProps) => { variant="outline" className={`font-semibold transition-colors duration-300 ${badgeClass} ${compact ? "px-2 py-0.5 text-xs" : "px-3 py-1 text-sm"}`} > - {activeBrakes === undefined ? "—" : activeBrakes ? "BRAKED" : "UNBRAKED"} + {brakesStatus === undefined ? "—" : String(brakesStatus)}
From ef9cd2b426516565f08ce6a8aacd2e1c02e49476 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 03:21:27 +0200 Subject: [PATCH 5/9] fix(competton-view): add celsisus to batteries --- frontend/competition-view/src/constants/measurements.ts | 4 ++++ .../src/pages/Batteries/components/HvBatterySection.tsx | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frontend/competition-view/src/constants/measurements.ts b/frontend/competition-view/src/constants/measurements.ts index 0d0e5951f..d726acd8b 100644 --- a/frontend/competition-view/src/constants/measurements.ts +++ b/frontend/competition-view/src/constants/measurements.ts @@ -66,6 +66,10 @@ export const HV_CURRENT_RANGE: readonly [number, number] = [0, 120]; export const LEV_CURRENT_RANGE: readonly [number, number] = [-55, 55]; /** Propulsion phase current interval (A). */ export const PROP_CURRENT_RANGE: readonly [number, number] = [0, 120]; +/** Battery temperature safety interval (°C). */ +export const TEMP_RANGE: readonly [number, number] = [0, 60]; +/** Propulsion velocity safety interval (km/h). */ +export const SPEED_RANGE: readonly [number, number] = [0, 50]; /** Cell voltage display range and warning thresholds (V), shared by the battery views. */ export const CELL_V_MIN = 3.0; diff --git a/frontend/competition-view/src/pages/Batteries/components/HvBatterySection.tsx b/frontend/competition-view/src/pages/Batteries/components/HvBatterySection.tsx index ebd46e4d2..cd057377e 100644 --- a/frontend/competition-view/src/pages/Batteries/components/HvBatterySection.tsx +++ b/frontend/competition-view/src/pages/Batteries/components/HvBatterySection.tsx @@ -6,6 +6,7 @@ import { CELL_V_WARN_LOW, HVBMS, PACK_V_RANGE, + TEMP_RANGE, } from "../../../constants/measurements"; import { useStaleFlags } from "../../../hooks/useIsStale"; import useMeasurement from "../../../hooks/useMeasurement"; @@ -87,8 +88,8 @@ const HvBatterySection = () => { range: CELL_V_RANGE, valueClass: typeof voltageMin === "number" && voltageMin < CELL_V_WARN_LOW ? "text-red-500" : "", }, - { label: "T max", value: fmt(tempMax), unit: "°C" }, - { label: "T min", value: fmt(tempMin), unit: "°C" }, + { label: "T max", value: fmt(tempMax), unit: "°C", range: TEMP_RANGE }, + { label: "T min", value: fmt(tempMin), unit: "°C", range: TEMP_RANGE }, ].map(({ label, value, unit, valueClass, range }, i) => (
{label} From 08a9dba3b5ae8580f0892a03f954b07d499facde Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 03:23:40 +0200 Subject: [PATCH 6/9] fix(competition-view): speed range --- frontend/competition-view/src/pages/Charts/Charts.tsx | 4 ++-- frontend/competition-view/src/pages/Overview/Overview.tsx | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/competition-view/src/pages/Charts/Charts.tsx b/frontend/competition-view/src/pages/Charts/Charts.tsx index 58a6d6977..b891adceb 100644 --- a/frontend/competition-view/src/pages/Charts/Charts.tsx +++ b/frontend/competition-view/src/pages/Charts/Charts.tsx @@ -6,7 +6,7 @@ import { MoveVertical, Zap, } from "lucide-react"; -import { BOARDS, HVBMS, LCU, PCU } from "../../constants/measurements"; +import { BOARDS, HVBMS, LCU, PCU, SPEED_RANGE } from "../../constants/measurements"; import MultiSeriesChart, { type SeriesConfig } from "./components/MultiSeriesChart"; import TelemetryChart from "./components/TelemetryChart"; @@ -60,7 +60,7 @@ const Charts = () => (
{/* Row 1 — Kinematic */} - + {/* Row 2 — Electrical */} diff --git a/frontend/competition-view/src/pages/Overview/Overview.tsx b/frontend/competition-view/src/pages/Overview/Overview.tsx index 48bf638c3..346a51a9c 100644 --- a/frontend/competition-view/src/pages/Overview/Overview.tsx +++ b/frontend/competition-view/src/pages/Overview/Overview.tsx @@ -21,6 +21,7 @@ import { PACK_V_RANGE, PCU, PROP_CURRENT_RANGE, + SPEED_RANGE, } from "../../constants/measurements"; import useMeasurement from "../../hooks/useMeasurement"; import { useIsStale, useStaleFlags } from "../../hooks/useIsStale"; @@ -382,7 +383,7 @@ const Dashboard = () => { {/* Charts — 2 cols × 3 rows */}
- + From 8f8f3f9ce4222ca6b5bf1f6adea0e2a2ca9af439 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 03:29:33 +0200 Subject: [PATCH 7/9] fix(competition-view): air gap range --- frontend/competition-view/src/constants/measurements.ts | 2 ++ frontend/competition-view/src/pages/Charts/Charts.tsx | 4 ++-- frontend/competition-view/src/pages/Overview/Overview.tsx | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/competition-view/src/constants/measurements.ts b/frontend/competition-view/src/constants/measurements.ts index d726acd8b..b22128fe5 100644 --- a/frontend/competition-view/src/constants/measurements.ts +++ b/frontend/competition-view/src/constants/measurements.ts @@ -70,6 +70,8 @@ export const PROP_CURRENT_RANGE: readonly [number, number] = [0, 120]; export const TEMP_RANGE: readonly [number, number] = [0, 60]; /** Propulsion velocity safety interval (km/h). */ export const SPEED_RANGE: readonly [number, number] = [0, 50]; +/** Vertical airgap safety interval (mm). */ +export const VERT_AIRGAP_RANGE: readonly [number, number] = [10, 25]; /** Cell voltage display range and warning thresholds (V), shared by the battery views. */ export const CELL_V_MIN = 3.0; diff --git a/frontend/competition-view/src/pages/Charts/Charts.tsx b/frontend/competition-view/src/pages/Charts/Charts.tsx index b891adceb..d4a59c784 100644 --- a/frontend/competition-view/src/pages/Charts/Charts.tsx +++ b/frontend/competition-view/src/pages/Charts/Charts.tsx @@ -6,7 +6,7 @@ import { MoveVertical, Zap, } from "lucide-react"; -import { BOARDS, HVBMS, LCU, PCU, SPEED_RANGE } from "../../constants/measurements"; +import { BOARDS, HVBMS, LCU, PCU, SPEED_RANGE, VERT_AIRGAP_RANGE } from "../../constants/measurements"; import MultiSeriesChart, { type SeriesConfig } from "./components/MultiSeriesChart"; import TelemetryChart from "./components/TelemetryChart"; @@ -71,7 +71,7 @@ const Charts = () => ( {/* Row 4 — Airgaps */} - + {/* Row 5 — Levitation currents */} diff --git a/frontend/competition-view/src/pages/Overview/Overview.tsx b/frontend/competition-view/src/pages/Overview/Overview.tsx index 346a51a9c..d08406ad3 100644 --- a/frontend/competition-view/src/pages/Overview/Overview.tsx +++ b/frontend/competition-view/src/pages/Overview/Overview.tsx @@ -22,6 +22,7 @@ import { PCU, PROP_CURRENT_RANGE, SPEED_RANGE, + VERT_AIRGAP_RANGE, } from "../../constants/measurements"; import useMeasurement from "../../hooks/useMeasurement"; import { useIsStale, useStaleFlags } from "../../hooks/useIsStale"; @@ -384,7 +385,7 @@ const Dashboard = () => {
- + From 768ffb8984ccde93576fc3a9e92cb0257cc52d90 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 08:39:04 +0200 Subject: [PATCH 8/9] fix(competition-view): new kernel setting --- electron-app/src/ipc/handlers.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/electron-app/src/ipc/handlers.js b/electron-app/src/ipc/handlers.js index 4e774ea72..fd3a646a3 100644 --- a/electron-app/src/ipc/handlers.js +++ b/electron-app/src/ipc/handlers.js @@ -68,8 +68,9 @@ function setupIpcHandlers() { * @event setup-kernel * @async * @description Configures kernel parameters needed by the backend - * (disables the TCP invalid-packet rate limit). Linux only; on other - * platforms it shows a warning dialog. + * (disables the TCP invalid-packet rate limit and lowers the TCP + * retransmission limit). Linux only; on other platforms it shows a + * warning dialog. * @returns {Promise<{success: boolean, message: string}>} */ ipcMain.handle("setup-kernel", async () => { @@ -88,6 +89,7 @@ function setupIpcHandlers() { "sysctl", "-w", "net.ipv4.tcp_invalid_ratelimit=0", + "net.ipv4.tcp_retries2=3", ]); logger.electron.info(`Kernel setup completed: ${stdout.trim()}`); return { success: true, message: "Kernel set up successfully" }; From e4359673ce63bb16dad746481479cf485ab36cb7 Mon Sep 17 00:00:00 2001 From: Javier Ribal del Rio Date: Fri, 17 Jul 2026 08:39:56 +0200 Subject: [PATCH 9/9] feat(competition-view): invalid order send log html --- .../src/constants/orderLimits.ts | 50 +++++++++++++++++++ .../Orders/components/OrderParameters.tsx | 48 +++++++++++++----- .../src/pages/Orders/components/OrderRow.tsx | 22 ++++++-- 3 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 frontend/competition-view/src/constants/orderLimits.ts diff --git a/frontend/competition-view/src/constants/orderLimits.ts b/frontend/competition-view/src/constants/orderLimits.ts new file mode 100644 index 000000000..f8f4749a3 --- /dev/null +++ b/frontend/competition-view/src/constants/orderLimits.ts @@ -0,0 +1,50 @@ +import type { CommandCatalogItem, NumericParameter } from "../types/catalog"; + +/** + * Per-order overrides for the numeric input limits reported by the backend + * catalog. Keyed by field key (the keys of `CommandCatalogItem.fields`); + * a `min`/`max` set here replaces the corresponding bound of the field's + * safeRange, so the input min/max, placeholder, out-of-range validation and + * error message all pick it up. `null` removes the bound; an omitted key + * keeps the catalog value. + */ +export interface FieldLimitChanges { + min?: number | null; + max?: number | null; +} + +export type OrderLimitOverride = [orderId: number, changes: Record]; + +export const ORDER_LIMIT_OVERRIDES: readonly OrderLimitOverride[] = [ + [100, { propulsion_current_reference: { min: 0, max: 120 } }], + [101, { levitation_target_height: { min: 5, max: 25 } }], + [102, { + propulsion_current_reference: { min: 0, max: 120 }, + levitation_target_height: { min: 5, max: 25 }, + }], +]; + +const overridesById = new Map(ORDER_LIMIT_OVERRIDES); + +const overrideSafeRange = (param: NumericParameter, changes: FieldLimitChanges): NumericParameter => ({ + ...param, + safeRange: [ + "min" in changes ? (changes.min ?? null) : param.safeRange[0], + "max" in changes ? (changes.max ?? null) : param.safeRange[1], + ], +}); + +/** Returns the item with ORDER_LIMIT_OVERRIDES applied to its numeric fields (the item itself if it has none). */ +export const applyOrderLimitOverrides = (item: CommandCatalogItem): CommandCatalogItem => { + const changes = overridesById.get(item.id); + if (!changes) return item; + + const fields = Object.fromEntries( + Object.entries(item.fields).map(([key, param]) => [ + key, + param.kind === "numeric" && changes[key] ? overrideSafeRange(param, changes[key]) : param, + ]), + ); + + return { ...item, fields }; +}; diff --git a/frontend/competition-view/src/pages/Orders/components/OrderParameters.tsx b/frontend/competition-view/src/pages/Orders/components/OrderParameters.tsx index fc31572ae..967e1ef7d 100644 --- a/frontend/competition-view/src/pages/Orders/components/OrderParameters.tsx +++ b/frontend/competition-view/src/pages/Orders/components/OrderParameters.tsx @@ -9,7 +9,21 @@ import { SelectTrigger, SelectValue, } from "@workspace/ui/components"; -import type { CommandParameter, ParameterValues } from "../../../types/catalog"; +import type { CommandParameter, NumericParameter, ParameterValues } from "../../../types/catalog"; + +/** True when the field holds a number outside the parameter's safeRange (empty/NaN doesn't count). */ +export const isNumericValueOutOfRange = (param: NumericParameter, raw: unknown): boolean => { + const value = parseFloat(String(raw)); + if (!Number.isFinite(value)) return false; + const [min, max] = param.safeRange; + return (min != null && value < min) || (max != null && value > max); +}; + +const rangeLabel = (min: number | null, max: number | null): string => { + if (min != null && max != null) return `between ${min} and ${max}`; + if (min != null) return `at least ${min}`; + return `at most ${max}`; +}; interface OrderParametersProps { fields: Record; @@ -24,17 +38,27 @@ const OrderParameters = ({ fields, values, onChange }: OrderParametersProps) => {param.name} {param.kind === "numeric" && ( - onChange(key, e.target.value)} - placeholder={ - param.safeRange[0] != null && param.safeRange[1] != null - ? `${param.safeRange[0]}–${param.safeRange[1]}` - : undefined - } - /> + <> + onChange(key, e.target.value)} + aria-invalid={isNumericValueOutOfRange(param, values[key]) || undefined} + placeholder={ + param.safeRange[0] != null && param.safeRange[1] != null + ? `${param.safeRange[0]}–${param.safeRange[1]}` + : undefined + } + /> + {isNumericValueOutOfRange(param, values[key]) && ( +

+ Must be {rangeLabel(param.safeRange[0], param.safeRange[1])}. +

+ )} + )} {param.kind === "enum" && (