diff --git a/README.md b/README.md
index 40127ae..d9aa9fc 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ Prefer bench testing and disconnected telemetry validation before using the soft
## Features
-- **Live map** with flight track (up to 5000 points), home reference, distance, bottom-center **Attitude HUD** (pitch ladder, roll arc, heading tape, speed/altitude, climb bar, armed/mode; dims with a **Stale** banner when live telemetry is older than 3 s), **map navigation toolbar** (Follow with heading-up/north-up, Recenter, Fit track; preferences in `uav-gcs.map.follow` / `uav-gcs.map.headingUp`), and in-app **basemap switcher** (Tactical / Satellite / Topo; persisted in `localStorage` as `uav-gcs.map.basemap`)
+- **Live map** with flight track (up to 5000 points), home reference, distance, **heading-aligned drone chevron** (falls back to a circle when heading is unknown), bottom-center **Attitude HUD** (pitch ladder, roll arc, heading tape, speed/altitude, climb bar, armed/mode; dims with a **Stale** banner when live telemetry is older than 3 s), **map navigation toolbar** (Follow with heading-up/north-up, Recenter, Fit track; preferences in `uav-gcs.map.follow` / `uav-gcs.map.headingUp`), and in-app **basemap switcher** (Tactical / Satellite / Topo; persisted in `localStorage` as `uav-gcs.map.basemap`)
- **Telemetry sidebar** — **Text** or **Inst** (mini gauges with the same telemetry fields as text mode); drag card headers (⠿) to reorder (shared order for both views, stored in `uav-gcs.sidebar.order`); **Reset** restores recommended flight-priority order; alerts stay fixed at the top
- **Serial link** — port picker (USB/PNP preferred), manual path entry, common baud rates
- **Activity log** — connection status, parser stats, frame message stats; actionable link-issue banners for serial denial, busy ports, silence after connect, and parser spikes
@@ -283,7 +283,7 @@ On the **desktop** link, gimbal attitude for estimation comes from MAVLink **285
| `VITE_BASE_PATH` | Optional asset base for hosted deploys (e.g. `/uav-gcs/`). Cloud builds default to `./` (relative) so subpath hosting works |
| `VITE_MAP_STYLE_URL` | Optional: full MapLibre style URL (hides the in-app basemap switcher) |
| `VITE_SATELLITE_TILE_URL` | Optional: custom raster tile URL for the **Satellite** preset (default: Esri World Imagery) |
-| `VITE_VIDEO_URL` / `VITE_VIDEO_KIND` | Optional: camera stream (e.g. MJPEG) |
+| `VITE_VIDEO_URL` / `VITE_VIDEO_KIND` | Optional: camera stream (e.g. MJPEG). Compact attitude HUD on the feed is toggled with **HUD** in the camera header (`uav-gcs.video.hud`). |
| `VITE_ENABLE_SPLASH_SCREEN` | Startup HUD splash overlay (default: enabled; set `false` to skip) |
Server: `PORT` (default `3001`), `HOST` (default `127.0.0.1`) in `apps/server`. The server exposes **unauthenticated** serial-control endpoints; it binds loopback only. Setting `HOST` to a routable address (e.g. `0.0.0.0`) is a deliberate opt-in that lets any device on the network open or close the link to flight hardware — see [`docs/adr/0002-server-loopback-only.md`](docs/adr/0002-server-loopback-only.md). On startup the server prints a prominent `console.warn` when bound beyond loopback; the browser stack shows a matching top banner when `VITE_API_BASE_URL` or `VITE_WS_URL` targets a non-loopback host. State-changing routes (`POST /api/connect`, `/api/disconnect`, `/api/reset`, logging start/stop) reject browser requests whose `Origin` is not the local Vite dev UI (`http://localhost:5173` or `http://127.0.0.1:5173`); non-browser clients that omit `Origin` are unchanged. `POST /api/connect` validates serial `path` against plausible device patterns only (Windows `COM*`, macOS `/dev/cu.*`/`/dev/tty.*`, Linux `tty*`, `/dev/serial/by-id|by-path/*`, `/dev/rfcomm*`) and `baudRate` (57600, 115200, 420000, 460800) before opening the port; malformed requests return HTTP 400. CI runs `cargo audit` on the desktop crate (`pnpm audit:desktop`).
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 3cc1543..4ac04d9 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@uav-ground-control-station/desktop",
- "version": "0.2.41",
+ "version": "0.2.43",
"private": true,
"license": "GPL-3.0-only",
"type": "module",
diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml
index bd2b6ae..5280fb2 100644
--- a/apps/desktop/src-tauri/Cargo.toml
+++ b/apps/desktop/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "uav-ground-control-station"
-version = "0.2.41"
+version = "0.2.43"
description = "Native desktop shell for UAV Ground Control Station"
authors = ["F. Eber"]
license = "GPL-3.0-only"
diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json
index c99b66b..6854490 100644
--- a/apps/desktop/src-tauri/tauri.conf.json
+++ b/apps/desktop/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "UAV Ground Control Station",
- "version": "0.2.41",
+ "version": "0.2.43",
"identifier": "com.uav.ground-control-station",
"build": {
"beforeDevCommand": "pnpm --filter @uav-ground-control-station/web dev",
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index b680619..0d2f3e7 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -204,7 +204,7 @@ export function App() {
/>
-
+
{activeSourceMode !== "live" && (
diff --git a/apps/web/src/components/HudOverlay.tsx b/apps/web/src/components/HudOverlay.tsx
index c9cb122..c35dc14 100644
--- a/apps/web/src/components/HudOverlay.tsx
+++ b/apps/web/src/components/HudOverlay.tsx
@@ -1,3 +1,4 @@
+import { useId } from "react";
import type { TelemetryState } from "@uav-ground-control-station/shared";
import { formatNumber } from "../lib/format";
import { resolveHeadingDeg } from "../lib/resolveHeadingDeg";
@@ -13,9 +14,12 @@ interface HudOverlayProps {
stale?: boolean;
/** Phase 2: smaller variant for video PiP. */
compact?: boolean;
+ /** When false, omit `data-tour="attitude-hud"` (e.g. compact PiP duplicate). */
+ showTourTarget?: boolean;
}
-export function HudOverlay({ telemetry, stale = false, compact = false }: HudOverlayProps) {
+export function HudOverlay({ telemetry, stale = false, compact = false, showTourTarget = true }: HudOverlayProps) {
+ const clipId = useId().replace(/:/g, "");
const size = compact ? 220 : 320;
const cx = size / 2;
const cy = size / 2;
@@ -39,7 +43,7 @@ export function HudOverlay({ telemetry, stale = false, compact = false }: HudOve
return (