From 2ba8b3b93d7c3e6119d3d0babf67708967a34972 Mon Sep 17 00:00:00 2001 From: ruv Date: Fri, 6 Mar 2026 15:52:52 -0500 Subject: [PATCH] docs: add ADR-052 Tauri desktop frontend with DDD bounded contexts Proposes a Tauri v2 desktop application as the primary UI for RuView, replacing 6+ CLI tools with a single cross-platform app. Covers hardware discovery, firmware flashing (espflash), OTA updates, WASM module management, sensing server control, and live visualization. Includes DDD domain model with 6 bounded contexts, aggregate definitions, domain events, and anti-corruption layers for ESP32 firmware APIs. Closes #177 Co-Authored-By: claude-flow --- docs/adr/ADR-052-ddd-bounded-contexts.md | 586 +++++++++++++++++ docs/adr/ADR-052-tauri-desktop-frontend.md | 714 +++++++++++++++++++++ 2 files changed, 1300 insertions(+) create mode 100644 docs/adr/ADR-052-ddd-bounded-contexts.md create mode 100644 docs/adr/ADR-052-tauri-desktop-frontend.md diff --git a/docs/adr/ADR-052-ddd-bounded-contexts.md b/docs/adr/ADR-052-ddd-bounded-contexts.md new file mode 100644 index 00000000..ff83d110 --- /dev/null +++ b/docs/adr/ADR-052-ddd-bounded-contexts.md @@ -0,0 +1,586 @@ +# ADR-052 Appendix: DDD Bounded Contexts — Tauri Desktop Frontend + +This document maps out the domain model for the RuView Tauri desktop application +described in ADR-052. It defines bounded contexts, their aggregates, entities, +value objects, and the domain events flowing between them. + +## Context Map + +``` ++-------------------+ +---------------------+ +--------------------+ +| | | | | | +| Device Discovery |------>| Firmware Management |------>| Configuration / | +| | | | | Provisioning | ++-------------------+ +---------------------+ +--------------------+ + | | | + | | | + v v v ++-------------------+ +---------------------+ +--------------------+ +| | | | | | +| Sensing Pipeline |<------| Edge Module | | Visualization | +| | | (WASM) | | | ++-------------------+ +---------------------+ +--------------------+ + +Relationship types: + -----> Upstream/Downstream (upstream publishes events, downstream consumes) + <----- Conformist (downstream conforms to upstream's model) +``` + +--- + +## 1. Device Discovery Context + +**Purpose**: Find, identify, and monitor ESP32 CSI nodes on the local network. + +**Upstream of**: Firmware Management, Configuration, Sensing Pipeline, Visualization + +### Aggregates + +#### `NodeRegistry` (Aggregate Root) + +Maintains the authoritative list of all known nodes. Merges discovery results +from multiple strategies (mDNS, UDP probe, HTTP sweep) and deduplicates by MAC +address. + +| Field | Type | Description | +|-------|------|-------------| +| `nodes` | `Map` | All discovered nodes keyed by MAC | +| `scan_state` | `ScanState` | Idle, Scanning, Error | +| `last_scan` | `DateTime` | Timestamp of last completed scan | + +**Invariant**: No two nodes may share the same MAC address. If a node is +discovered via multiple strategies, the most recent data wins. + +#### `Node` (Entity) + +| Field | Type | Description | +|-------|------|-------------| +| `mac` | `MacAddress` (VO) | IEEE 802.11 MAC address (unique identity) | +| `ip` | `IpAddr` | Current IP address (may change on DHCP renewal) | +| `hostname` | `Option` | mDNS hostname | +| `node_id` | `u8` | NVS-provisioned node ID | +| `firmware_version` | `Option` | Firmware version string | +| `health` | `HealthStatus` (VO) | Online / Offline / Degraded | +| `discovery_method` | `DiscoveryMethod` (VO) | How this node was found | +| `last_seen` | `DateTime` | Last successful contact | +| `tdm_config` | `Option` (VO) | TDM slot assignment | +| `edge_tier` | `Option` | Edge processing tier (0/1/2) | + +### Value Objects + +- `MacAddress` — 6-byte hardware address, formatted as `AA:BB:CC:DD:EE:FF` +- `HealthStatus` — enum: `Online`, `Offline`, `Degraded(reason: String)` +- `DiscoveryMethod` — enum: `Mdns`, `UdpProbe`, `HttpSweep`, `Manual` +- `TdmConfig` — `{ slot_index: u8, total_nodes: u8 }` +- `SemVer` — semantic version `major.minor.patch` + +### Domain Events + +| Event | Payload | Consumers | +|-------|---------|-----------| +| `NodeDiscovered` | `{ node: Node }` | Firmware Mgmt (check for updates), Visualization (add to mesh graph) | +| `NodeWentOffline` | `{ mac: MacAddress, last_seen: DateTime }` | Visualization (gray out node), Sensing Pipeline (remove from active set) | +| `NodeCameOnline` | `{ node: Node }` | Visualization (restore node), Sensing Pipeline (re-add) | +| `NodeHealthChanged` | `{ mac: MacAddress, old: HealthStatus, new: HealthStatus }` | Visualization (update indicator) | +| `ScanCompleted` | `{ found: usize, new: usize, lost: usize }` | Dashboard (update summary) | + +### Anti-Corruption Layer + +When receiving data from the ESP32 OTA status endpoint (`GET /ota/status`), the +response format is owned by the firmware and may change across firmware versions. +The ACL translates the raw JSON response into `Node` entity fields: + +```rust +/// ACL: Translate ESP32 OTA status response to Node fields. +fn translate_ota_status(raw: &serde_json::Value) -> Result { + NodePatch { + firmware_version: raw["version"].as_str().map(SemVer::parse).transpose()?, + uptime_secs: raw["uptime_s"].as_u64(), + free_heap: raw["free_heap"].as_u64(), + // Firmware may add fields in future versions — unknown fields are ignored + } +} +``` + +--- + +## 2. Firmware Management Context + +**Purpose**: Flash, update, and verify firmware on ESP32 nodes. + +**Upstream of**: Configuration (a fresh flash triggers provisioning) +**Downstream of**: Device Discovery (needs node list and serial port info) + +### Aggregates + +#### `FlashSession` (Aggregate Root) + +Represents a single firmware flashing operation from start to completion. Each +session has a lifecycle: Created -> Connecting -> Erasing -> Writing -> Verifying -> +Completed | Failed. + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `Uuid` | Session identifier | +| `port` | `SerialPort` (VO) | Target serial port | +| `firmware` | `FirmwareBinary` (Entity) | The binary being flashed | +| `chip` | `ChipType` (VO) | Target chip (ESP32, ESP32-S3, ESP32-C3) | +| `phase` | `FlashPhase` (VO) | Current phase of the flash operation | +| `progress` | `Progress` (VO) | Bytes written / total, speed | +| `started_at` | `DateTime` | When the session started | +| `error` | `Option` | Error message if failed | + +**Invariant**: Only one `FlashSession` may be active per serial port at a time. + +#### `FirmwareBinary` (Entity) + +| Field | Type | Description | +|-------|------|-------------| +| `path` | `PathBuf` | Filesystem path to the `.bin` file | +| `size_bytes` | `u64` | Binary size | +| `version` | `Option` | Extracted from ESP32 image header | +| `chip_type` | `Option` | Detected from image magic bytes | +| `checksum` | `Sha256Hash` (VO) | SHA-256 of the binary | + +#### `OtaSession` (Aggregate Root) + +Represents an over-the-air firmware update to a running node. + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `Uuid` | Session identifier | +| `target_node` | `MacAddress` | Target node MAC | +| `target_ip` | `IpAddr` | Target node IP | +| `firmware` | `FirmwareBinary` | The binary being pushed | +| `psk` | `Option` | PSK for authentication (ADR-050) | +| `phase` | `OtaPhase` | Uploading / Rebooting / Verifying / Done / Failed | +| `progress` | `Progress` | Upload progress | + +### Value Objects + +- `SerialPort` — `{ name: String, vid: u16, pid: u16, manufacturer: Option }` +- `ChipType` — enum: `Esp32`, `Esp32s3`, `Esp32c3` +- `FlashPhase` — enum: `Connecting`, `Erasing`, `Writing`, `Verifying`, `Completed`, `Failed` +- `OtaPhase` — enum: `Uploading`, `Rebooting`, `Verifying`, `Completed`, `Failed` +- `Progress` — `{ bytes_done: u64, bytes_total: u64, speed_bps: u64 }` +- `Sha256Hash` — 32-byte hash +- `SecureString` — zeroized-on-drop string for PSK tokens + +### Domain Events + +| Event | Payload | Consumers | +|-------|---------|-----------| +| `FlashStarted` | `{ session_id, port, firmware_version }` | UI (show progress) | +| `FlashProgress` | `{ session_id, phase, progress }` | UI (update progress bar) | +| `FlashCompleted` | `{ session_id, duration_secs }` | Configuration (trigger provisioning prompt) | +| `FlashFailed` | `{ session_id, error }` | UI (show error) | +| `OtaStarted` | `{ session_id, target_mac, firmware_version }` | Discovery (mark node as updating) | +| `OtaCompleted` | `{ session_id, target_mac, new_version }` | Discovery (refresh node info) | +| `OtaFailed` | `{ session_id, target_mac, error }` | UI (show error) | + +### Anti-Corruption Layer + +The `espflash` crate has its own error types and progress reporting model. The +ACL translates these into domain events: + +```rust +/// ACL: Translate espflash progress callbacks to domain FlashProgress events. +impl From for FlashProgress { + fn from(msg: espflash::ProgressCallbackMessage) -> Self { + match msg { + espflash::ProgressCallbackMessage::Connecting => FlashProgress { + phase: FlashPhase::Connecting, + progress: Progress::indeterminate(), + }, + espflash::ProgressCallbackMessage::Erasing { addr, total } => FlashProgress { + phase: FlashPhase::Erasing, + progress: Progress::new(addr as u64, total as u64), + }, + // ... etc + } + } +} +``` + +--- + +## 3. Configuration / Provisioning Context + +**Purpose**: Manage NVS configuration for ESP32 nodes — WiFi credentials, network +targets, TDM mesh settings, edge intelligence parameters, WASM security keys. + +**Downstream of**: Device Discovery (needs serial port), Firmware Management (post-flash provisioning) + +### Aggregates + +#### `ProvisioningSession` (Aggregate Root) + +Represents a single NVS write or read operation on a connected ESP32. + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `Uuid` | Session identifier | +| `port` | `SerialPort` (VO) | Target serial port | +| `config` | `NodeConfig` (Entity) | Configuration to write | +| `direction` | `Direction` | Read or Write | +| `phase` | `ProvisionPhase` | Generating / Flashing / Verifying / Done | + +#### `NodeConfig` (Entity) + +The full set of NVS key-value pairs for a single node. Maps directly to the +firmware's `nvs_config_t` struct (see `firmware/esp32-csi-node/main/nvs_config.h`). + +| Field | Type | NVS Key | Description | +|-------|------|---------|-------------| +| `wifi_ssid` | `Option` | `ssid` | WiFi SSID | +| `wifi_password` | `Option` | `password` | WiFi password | +| `target_ip` | `Option` | `target_ip` | Aggregator IP | +| `target_port` | `Option` | `target_port` | Aggregator UDP port | +| `node_id` | `Option` | `node_id` | Node identifier | +| `tdm_slot` | `Option` | `tdm_slot` | TDM slot index | +| `tdm_total` | `Option` | `tdm_nodes` | Total TDM nodes | +| `edge_tier` | `Option` | `edge_tier` | Processing tier | +| `hop_count` | `Option` | `hop_count` | Channel hop count | +| `channel_list` | `Option>` | `chan_list` | Channel sequence | +| `dwell_ms` | `Option` | `dwell_ms` | Hop dwell time | +| `power_duty` | `Option` | `power_duty` | Power duty cycle | +| `presence_thresh` | `Option` | `pres_thresh` | Presence threshold | +| `fall_thresh` | `Option` | `fall_thresh` | Fall detection threshold | +| `vital_window` | `Option` | `vital_win` | Vital sign window | +| `vital_interval_ms` | `Option` | `vital_int` | Vital sign interval | +| `top_k_count` | `Option` | `subk_count` | Top-K subcarriers | +| `wasm_max_modules` | `Option` | `wasm_max` | Max WASM modules | +| `wasm_verify` | `Option` | `wasm_verify` | Require WASM signature | +| `wasm_pubkey` | `Option<[u8; 32]>` | `wasm_pubkey` | Ed25519 public key | +| `ota_psk` | `Option` | `ota_psk` | OTA pre-shared key | + +**Invariant**: `tdm_slot < tdm_total` when both are set. +**Invariant**: `channel_list.len() == hop_count` when both are set. +**Invariant**: `10 <= power_duty <= 100`. + +#### `MeshConfig` (Entity) + +A mesh-level configuration that generates per-node `NodeConfig` instances. +Corresponds to ADR-044 Phase 2 (config file provisioning). + +| Field | Type | Description | +|-------|------|-------------| +| `common` | `NodeConfig` | Shared settings (WiFi, target IP, edge tier) | +| `nodes` | `Vec` | Per-node overrides (port, node_id, tdm_slot) | + +```rust +pub struct MeshNodeEntry { + pub port: String, + pub node_id: u8, + pub tdm_slot: u8, + // All other fields inherited from common +} +``` + +**Invariant**: `tdm_total` is automatically computed as `nodes.len()`. + +### Value Objects + +- `ProvisionPhase` — enum: `Generating`, `Flashing`, `Verifying`, `Completed`, `Failed` +- `Direction` — enum: `Read`, `Write` +- `Preset` — enum: `Basic`, `Vitals`, `Mesh3`, `Mesh6Vitals` (ADR-044 Phase 3) + +### Domain Events + +| Event | Payload | Consumers | +|-------|---------|-----------| +| `NodeProvisioned` | `{ port, node_id, config_summary }` | Discovery (trigger re-scan), UI (show success) | +| `NvsReadCompleted` | `{ port, config: NodeConfig }` | UI (populate form) | +| `ProvisionFailed` | `{ port, error }` | UI (show error) | +| `MeshProvisionStarted` | `{ node_count }` | UI (show batch progress) | +| `MeshProvisionCompleted` | `{ success_count, fail_count }` | UI (show summary) | + +--- + +## 4. Sensing Pipeline Context + +**Purpose**: Control the sensing server process, receive real-time CSI data, and +manage the signal processing pipeline. + +**Downstream of**: Device Discovery (needs node IPs for data attribution) + +### Aggregates + +#### `SensingServer` (Aggregate Root) + +Represents the managed sensing server child process. + +| Field | Type | Description | +|-------|------|-------------| +| `state` | `ServerState` (VO) | Stopped / Starting / Running / Stopping / Crashed | +| `config` | `ServerConfig` (VO) | Port configuration, log level, model paths | +| `pid` | `Option` | OS process ID when running | +| `started_at` | `Option>` | Start timestamp | +| `log_buffer` | `RingBuffer` | Last N log lines | +| `ws_url` | `Option` | WebSocket URL for live data | + +**Invariant**: Only one `SensingServer` process may be managed at a time. + +#### `SensingSession` (Entity) + +An active connection to the sensing server's WebSocket for receiving real-time data. + +| Field | Type | Description | +|-------|------|-------------| +| `connection_state` | `WsState` | Connecting / Connected / Disconnected | +| `frames_received` | `u64` | Total CSI frames received this session | +| `last_frame_at` | `Option>` | Timestamp of last received frame | +| `subscriptions` | `HashSet` | Which data streams are active | + +### Value Objects + +- `ServerState` — enum: `Stopped`, `Starting`, `Running`, `Stopping`, `Crashed(exit_code: i32)` +- `ServerConfig` — `{ http_port: u16, ws_port: u16, udp_port: u16, model_dir: PathBuf, log_level: Level }` +- `LogEntry` — `{ timestamp: DateTime, level: Level, target: String, message: String }` +- `DataChannel` — enum: `CsiFrames`, `PoseUpdates`, `VitalSigns`, `ActivityClassification` +- `WsState` — enum: `Connecting`, `Connected`, `Disconnected(reason: String)` + +### Domain Events + +| Event | Payload | Consumers | +|-------|---------|-----------| +| `ServerStarted` | `{ pid, ports: ServerConfig }` | UI (enable sensing view), Discovery (start health polling via WS) | +| `ServerStopped` | `{ exit_code, uptime_secs }` | UI (disable sensing view) | +| `ServerCrashed` | `{ exit_code, last_log_lines }` | UI (show crash report) | +| `CsiFrameReceived` | `{ node_id, timestamp, subcarrier_count }` | Visualization (update charts) | +| `PoseUpdated` | `{ persons: Vec }` | Visualization (draw skeletons) | +| `VitalSignUpdate` | `{ node_id, bpm, breath_rate }` | Visualization (update vitals chart) | +| `ActivityDetected` | `{ label, confidence }` | Visualization (show activity) | + +--- + +## 5. Edge Module (WASM) Context + +**Purpose**: Upload, manage, and monitor WASM edge processing modules running +on ESP32 nodes. + +**Downstream of**: Device Discovery (needs node IPs and WASM capability info) +**Upstream of**: Sensing Pipeline (WASM modules emit edge-processed events) + +### Aggregates + +#### `ModuleRegistry` (Aggregate Root) + +Tracks all WASM modules across all nodes. + +| Field | Type | Description | +|-------|------|-------------| +| `modules` | `Map<(MacAddress, ModuleId), WasmModule>` | Per-node module inventory | + +#### `WasmModule` (Entity) + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `ModuleId` (VO) | Node-assigned module identifier | +| `name` | `String` | Filename of the uploaded `.wasm` | +| `size_bytes` | `u64` | Module size | +| `status` | `ModuleStatus` (VO) | Loaded / Running / Stopped / Error | +| `node_mac` | `MacAddress` | Which node this module runs on | +| `uploaded_at` | `DateTime` | Upload timestamp | +| `signed` | `bool` | Whether the module has an Ed25519 signature | + +### Value Objects + +- `ModuleId` — string identifier assigned by the node firmware +- `ModuleStatus` — enum: `Loaded`, `Running`, `Stopped`, `Error(String)` + +### Domain Events + +| Event | Payload | Consumers | +|-------|---------|-----------| +| `ModuleUploaded` | `{ node_mac, module_id, name, size }` | UI (refresh list) | +| `ModuleStarted` | `{ node_mac, module_id }` | UI (update status) | +| `ModuleStopped` | `{ node_mac, module_id }` | UI (update status) | +| `ModuleUnloaded` | `{ node_mac, module_id }` | UI (remove from list) | +| `ModuleError` | `{ node_mac, module_id, error }` | UI (show error) | + +### Anti-Corruption Layer + +The ESP32 WASM management HTTP API (`/wasm/*` on port 8032) returns raw JSON +with firmware-specific field names. The ACL normalizes these: + +```rust +/// ACL: Translate ESP32 WASM list response to domain WasmModule entities. +fn translate_wasm_list(raw: &[serde_json::Value]) -> Vec { + raw.iter().filter_map(|entry| { + Some(WasmModule { + id: ModuleId(entry["id"].as_str()?.to_string()), + name: entry["name"].as_str().unwrap_or("unknown").to_string(), + size_bytes: entry["size"].as_u64().unwrap_or(0), + status: match entry["state"].as_str() { + Some("running") => ModuleStatus::Running, + Some("stopped") => ModuleStatus::Stopped, + Some("loaded") => ModuleStatus::Loaded, + other => ModuleStatus::Error( + format!("Unknown state: {:?}", other) + ), + }, + // ... + }) + }).collect() +} +``` + +--- + +## 6. Visualization Context + +**Purpose**: Render real-time and historical sensing data — CSI heatmaps, pose +skeletons, vital sign charts, mesh topology graphs. + +**Downstream of**: Sensing Pipeline (receives data events), Device Discovery (needs +node metadata for labeling) + +This context is **purely presentational** and contains no domain logic. It +transforms domain events from other contexts into visual representations. + +### Aggregates + +None — this context is a **Query Model** (CQRS read side). It subscribes to +domain events and projects them into view models. + +### View Models + +#### `DashboardView` + +| Field | Source Context | Description | +|-------|---------------|-------------| +| `nodes` | Device Discovery | Node cards with health, version, signal quality | +| `server` | Sensing Pipeline | Server status, uptime, port info | +| `recent_activity` | All contexts | Timeline of recent events | + +#### `SignalView` + +| Field | Source Context | Description | +|-------|---------------|-------------| +| `csi_heatmap` | Sensing Pipeline | Subcarrier amplitude x time matrix | +| `signal_field` | Sensing Pipeline | 2D signal strength grid | +| `activity_label` | Sensing Pipeline | Current classification | +| `confidence` | Sensing Pipeline | Classification confidence | + +#### `PoseView` + +| Field | Source Context | Description | +|-------|---------------|-------------| +| `persons` | Sensing Pipeline | Array of detected person skeletons | +| `zones` | Sensing Pipeline | Active zones in the sensing area | + +#### `VitalsView` + +| Field | Source Context | Description | +|-------|---------------|-------------| +| `breathing_rate_bpm` | Sensing Pipeline | Per-node breathing rate time series | +| `heart_rate_bpm` | Sensing Pipeline | Per-node heart rate time series | + +#### `MeshView` + +| Field | Source Context | Description | +|-------|---------------|-------------| +| `nodes` | Device Discovery | Positioned nodes for graph layout | +| `edges` | Device Discovery | Inter-node visibility/connectivity | +| `tdm_timeline` | Device Discovery | TDM slot schedule visualization | +| `sync_status` | Sensing Pipeline | Per-node sync status with server | + +--- + +## Cross-Context Event Flow + +``` + NodeDiscovered +Device Discovery ─────────────────────────────────> Firmware Management + │ │ + │ NodeDiscovered │ FlashCompleted + │ NodeHealthChanged │ + ├──────────────────> Visualization v + │ Configuration + │ NodeDiscovered │ + ├──────────────────> Sensing Pipeline │ NodeProvisioned + │ │ + │ v + │ Device Discovery + │ (re-scan triggered) + │ + │ NodeDiscovered + └──────────────────> Edge Module (WASM) + │ + │ ModuleUploaded, ModuleStarted + │ + v + Sensing Pipeline + │ + │ CsiFrameReceived, PoseUpdated, VitalSignUpdate + │ + v + Visualization +``` + +## Implementation Notes + +1. **Event Bus**: Domain events are dispatched via Tauri's event system + (`app_handle.emit("event-name", payload)`). The frontend subscribes using + `listen("event-name", callback)`. This provides natural cross-context + communication without coupling contexts directly. + +2. **State Isolation**: Each bounded context maintains its own `State<'_, T>` + managed by Tauri. Contexts do not share mutable state directly — they + communicate exclusively through events. + +3. **Module Organization**: Each bounded context maps to a Rust module under + `src/commands/` and `src/domain/`: + + ``` + src/ + commands/ # Tauri command handlers (application layer) + discovery.rs # Device Discovery context commands + flash.rs # Firmware Management context commands + ota.rs # Firmware Management context commands + provision.rs # Configuration context commands + server.rs # Sensing Pipeline context commands + wasm.rs # Edge Module context commands + domain/ # Domain models (pure Rust, no Tauri dependency) + discovery/ + mod.rs + node.rs # Node entity, MacAddress VO + registry.rs # NodeRegistry aggregate + events.rs # Discovery domain events + firmware/ + mod.rs + binary.rs # FirmwareBinary entity + flash.rs # FlashSession aggregate + ota.rs # OtaSession aggregate + events.rs + config/ + mod.rs + nvs.rs # NodeConfig entity + mesh.rs # MeshConfig entity + provision.rs # ProvisioningSession aggregate + events.rs + sensing/ + mod.rs + server.rs # SensingServer aggregate + session.rs # SensingSession entity + events.rs + wasm/ + mod.rs + module.rs # WasmModule entity + registry.rs # ModuleRegistry aggregate + events.rs + acl/ # Anti-corruption layers + ota_status.rs # ESP32 OTA status response translator + wasm_api.rs # ESP32 WASM API response translator + espflash.rs # espflash crate adapter + ``` + +4. **Testing Strategy**: Domain modules under `src/domain/` have no Tauri + dependency and can be tested with standard `cargo test`. Command handlers + under `src/commands/` require Tauri test utilities for integration testing. + +5. **Shared Kernel**: The `MacAddress`, `SemVer`, and `SecureString` value objects + are shared across contexts. They live in a `src/domain/shared.rs` module. + This is acceptable because they are immutable value objects with no behavior + beyond validation and formatting. diff --git a/docs/adr/ADR-052-tauri-desktop-frontend.md b/docs/adr/ADR-052-tauri-desktop-frontend.md new file mode 100644 index 00000000..4f5c470d --- /dev/null +++ b/docs/adr/ADR-052-tauri-desktop-frontend.md @@ -0,0 +1,714 @@ +# ADR-052: Tauri Desktop Frontend — RuView Hardware Management & Visualization + +| Field | Value | +|-------|-------| +| Status | Proposed | +| Date | 2026-03-06 | +| Deciders | ruv | +| Depends on | ADR-012 (ESP32 CSI Mesh), ADR-039 (Edge Intelligence), ADR-040 (WASM Programmable Sensing), ADR-044 (Provisioning Enhancements), ADR-050 (Security Hardening), ADR-051 (Server Decomposition) | +| Issue | [#177](https://github.com/ruvnet/RuView/issues/177) | + +## Context + +RuView currently requires users to interact with multiple disconnected tools to manage a WiFi DensePose deployment: + +| Task | Current Tool | Pain Point | +|------|-------------|------------| +| Flash firmware | `esptool.py` CLI | Requires Python, pip, correct chip/baud flags | +| Provision NVS | `provision.py` CLI | 13+ flags, no GUI, no read-back | +| OTA update | `curl POST :8032/ota` | Manual HTTP, PSK header construction | +| WASM modules | `curl` to `:8032/wasm/*` | No visibility into module state | +| Start sensing server | `cargo run` or binary | Manual port configuration, no log viewer | +| View sensing data | Browser at `localhost:8080` | Separate window, no hardware context | +| Mesh topology | Mental model | No visualization of TDM slots, sync, health | +| Node discovery | Manual IP tracking | No mDNS/UDP broadcast discovery | + +There is no single tool that provides a unified view of the entire deployment — from ESP32 hardware through the sensing pipeline to pose visualization. Field operators deploying multi-node meshes must context-switch between terminals, browsers, and serial monitors. + +### Why a Desktop App + +A browser-based UI cannot access serial ports (for flashing), raw UDP sockets (for node discovery), or the local filesystem (for firmware binaries). A desktop application is required for hardware management. Tauri v2 is the natural choice because: + +1. **Rust backend** — integrates directly with the existing Rust workspace (`wifi-densepose-rs`). Crates like `wifi-densepose-hardware` (serial port parsing), `wifi-densepose-config`, and `wifi-densepose-sensing-server` can be linked as library dependencies. +2. **Small binary** — Tauri bundles the system webview rather than shipping Chromium (~150 MB savings vs Electron). +3. **Cross-platform** — Windows, macOS, Linux from the same codebase. +4. **Security model** — Tauri's capability-based permissions system restricts frontend access to explicitly allowed Rust commands. + +### Why Not Electron / Flutter / Native + +| Option | Rejected Because | +|--------|-----------------| +| Electron | 150+ MB bundle, no Rust integration, duplicates webview | +| Flutter | No serial port plugins, Dart FFI to Rust is awkward | +| Native (GTK/Qt) | Platform-specific UI code, no web component reuse | +| Web-only (PWA) | Cannot access serial ports or raw UDP | + +## Decision + +Build a Tauri v2 desktop application as a new crate in the Rust workspace. The frontend uses TypeScript with React and Vite. The Rust backend exposes Tauri commands that bridge the frontend to serial ports, UDP sockets, HTTP management endpoints, and the sensing server process. + +### 1. Workspace Integration + +Add a new crate to the workspace: + +``` +rust-port/wifi-densepose-rs/ + Cargo.toml # Add "crates/wifi-densepose-desktop" to members + crates/ + wifi-densepose-desktop/ # NEW — Tauri app crate + Cargo.toml + tauri.conf.json + capabilities/ + default.json # Tauri v2 capability permissions + icons/ # App icons (all platforms) + src/ + main.rs # Tauri entry point + lib.rs # Command module re-exports + commands/ + mod.rs + discovery.rs # Node discovery commands + flash.rs # Firmware flashing commands + ota.rs # OTA update commands + wasm.rs # WASM module management commands + server.rs # Sensing server lifecycle commands + provision.rs # NVS provisioning commands + serial.rs # Serial port enumeration + state.rs # Tauri managed state + discovery/ + mod.rs + mdns.rs # mDNS service discovery + udp_broadcast.rs # UDP broadcast probe + flash/ + mod.rs + espflash.rs # Rust-native ESP32 flashing (via espflash crate) + esptool.rs # Fallback: bundled esptool.py wrapper + frontend/ + package.json + tsconfig.json + vite.config.ts + index.html + src/ + main.tsx + App.tsx + routes.tsx + hooks/ + useNodes.ts # Node discovery and status polling + useServer.ts # Sensing server state + useWebSocket.ts # WS connection to sensing server + stores/ + nodeStore.ts # Zustand store for discovered nodes + serverStore.ts # Sensing server process state + settingsStore.ts # User preferences (dark mode, ports) + pages/ + Dashboard.tsx # Hardware management overview + NodeDetail.tsx # Single node detail + config + FlashFirmware.tsx # Firmware flashing wizard + WasmModules.tsx # WASM module manager + SensingView.tsx # Live sensing data visualization + MeshTopology.tsx # Multi-node mesh topology view + Settings.tsx # App settings and preferences + components/ + NodeCard.tsx # Node status card (health, version, signal) + NodeList.tsx # Discovered node list + FirmwareProgress.tsx # Flash/OTA progress indicator + LogViewer.tsx # Scrolling log output + SignalChart.tsx # Real-time CSI signal chart + PoseOverlay.tsx # Pose skeleton overlay + MeshGraph.tsx # D3/force-graph mesh topology + SerialPortSelect.tsx # Serial port dropdown + ProvisionForm.tsx # NVS provisioning form + lib/ + tauri.ts # Typed Tauri invoke wrappers + types.ts # Shared TypeScript types +``` + +### 2. Rust Backend — Tauri Commands + +#### 2.1 Node Discovery + +```rust +// commands/discovery.rs + +/// Discover ESP32 CSI nodes on the local network. +/// Strategy 1: mDNS — nodes announce _ruview._tcp service +/// Strategy 2: UDP broadcast probe on port 5005 (CSI aggregator port) +/// Strategy 3: HTTP health check sweep on port 8032 (OTA server) +#[tauri::command] +async fn discover_nodes(timeout_ms: u64) -> Result, String>; + +/// Get detailed status from a specific node via HTTP. +/// Calls GET /ota/status on port 8032. +#[tauri::command] +async fn get_node_status(ip: String) -> Result; + +/// Subscribe to node health updates (periodic polling). +#[tauri::command] +async fn watch_nodes(interval_ms: u64, state: State<'_, AppState>) -> Result<(), String>; +``` + +The `DiscoveredNode` struct: + +```rust +#[derive(Serialize, Deserialize, Clone)] +pub struct DiscoveredNode { + pub ip: String, + pub mac: Option, + pub hostname: Option, + pub node_id: u8, + pub firmware_version: Option, + pub tdm_slot: Option, + pub tdm_total: Option, + pub edge_tier: Option, + pub uptime_secs: Option, + pub discovery_method: DiscoveryMethod, // Mdns | UdpProbe | HttpSweep + pub last_seen: chrono::DateTime, +} +``` + +#### 2.2 Firmware Flashing + +```rust +// commands/flash.rs + +/// List available serial ports with chip detection. +#[tauri::command] +async fn list_serial_ports() -> Result, String>; + +/// Flash firmware binary to an ESP32 via serial port. +/// Uses the `espflash` crate for Rust-native flashing (no Python dependency). +/// Falls back to bundled esptool.py if espflash fails. +/// Emits progress events via Tauri event system. +#[tauri::command] +async fn flash_firmware( + port: String, + firmware_path: String, + chip: Chip, // Esp32, Esp32s3, Esp32c3 + baud: Option, + app_handle: AppHandle, +) -> Result; + +/// Read firmware info from a connected ESP32 (chip type, flash size, MAC). +#[tauri::command] +async fn read_chip_info(port: String) -> Result; +``` + +Flash progress is emitted as Tauri events: + +```rust +#[derive(Serialize, Clone)] +pub struct FlashProgress { + pub phase: FlashPhase, // Connecting | Erasing | Writing | Verifying + pub progress_pct: f32, // 0.0 - 100.0 + pub bytes_written: u64, + pub bytes_total: u64, + pub speed_bps: u64, +} +``` + +#### 2.3 OTA Updates + +```rust +// commands/ota.rs + +/// Push firmware to a node via HTTP OTA (port 8032). +/// Includes PSK authentication per ADR-050. +#[tauri::command] +async fn ota_update( + node_ip: String, + firmware_path: String, + psk: Option, + app_handle: AppHandle, +) -> Result; + +/// Get OTA status from a node (current version, partition info). +#[tauri::command] +async fn ota_status(node_ip: String, psk: Option) -> Result; + +/// Batch OTA update — push firmware to multiple nodes sequentially. +/// Skips nodes already running the target version. +#[tauri::command] +async fn ota_batch_update( + nodes: Vec, // IPs + firmware_path: String, + psk: Option, + app_handle: AppHandle, +) -> Result, String>; +``` + +#### 2.4 WASM Module Management + +```rust +// commands/wasm.rs + +/// List WASM modules loaded on a node. +/// Calls GET /wasm/list on port 8032. +#[tauri::command] +async fn wasm_list(node_ip: String) -> Result, String>; + +/// Upload a WASM module to a node. +/// Calls POST /wasm/upload on port 8032 with binary payload. +#[tauri::command] +async fn wasm_upload( + node_ip: String, + wasm_path: String, + app_handle: AppHandle, +) -> Result; + +/// Start/stop a WASM module on a node. +#[tauri::command] +async fn wasm_control( + node_ip: String, + module_id: String, + action: WasmAction, // Start | Stop | Unload +) -> Result<(), String>; +``` + +#### 2.5 Sensing Server Lifecycle + +```rust +// commands/server.rs + +/// Start the sensing server as a managed child process. +/// The server binary is either bundled with the Tauri app (sidecar) +/// or discovered on PATH. +#[tauri::command] +async fn start_server( + config: ServerConfig, + state: State<'_, AppState>, + app_handle: AppHandle, +) -> Result<(), String>; + +/// Stop the managed sensing server process. +#[tauri::command] +async fn stop_server(state: State<'_, AppState>) -> Result<(), String>; + +/// Get sensing server status (running/stopped, PID, ports, uptime). +#[tauri::command] +async fn server_status(state: State<'_, AppState>) -> Result; + +#[derive(Serialize, Deserialize, Clone)] +pub struct ServerConfig { + pub http_port: u16, // Default: 8080 + pub ws_port: u16, // Default: 8765 + pub udp_port: u16, // Default: 5005 + pub static_dir: Option, // Path to UI static files + pub model_dir: Option, // Path to ML models + pub log_level: String, // trace, debug, info, warn, error +} +``` + +The sensing server is bundled as a Tauri sidecar binary. Tauri v2 supports sidecar binaries via `externalBin` in `tauri.conf.json`: + +```json +{ + "bundle": { + "externalBin": ["sensing-server"] + } +} +``` + +#### 2.6 NVS Provisioning + +```rust +// commands/provision.rs + +/// Provision NVS configuration to an ESP32 via serial port. +/// Replaces the Python provision.py script with a Rust-native implementation. +/// Generates NVS partition binary and flashes it to the NVS partition offset. +#[tauri::command] +async fn provision_node( + port: String, + config: NvsConfig, + app_handle: AppHandle, +) -> Result; + +/// Read current NVS configuration from a connected ESP32. +/// Reads the NVS partition and parses key-value pairs. +#[tauri::command] +async fn read_nvs(port: String) -> Result; + +#[derive(Serialize, Deserialize, Clone)] +pub struct NvsConfig { + pub wifi_ssid: Option, + pub wifi_password: Option, + pub target_ip: Option, + pub target_port: Option, + pub node_id: Option, + pub tdm_slot: Option, + pub tdm_total: Option, + pub edge_tier: Option, + pub presence_thresh: Option, + pub fall_thresh: Option, + pub vital_window: Option, + pub vital_interval_ms: Option, + pub top_k_count: Option, + pub hop_count: Option, + pub channel_list: Option>, + pub dwell_ms: Option, + pub power_duty: Option, + pub wasm_max_modules: Option, + pub wasm_verify: Option, + pub wasm_pubkey: Option>, + pub ota_psk: Option, +} +``` + +### 3. Frontend Architecture + +#### 3.1 Tech Stack + +| Layer | Choice | Rationale | +|-------|--------|-----------| +| Framework | React 19 | Component model, ecosystem, team familiarity | +| Build | Vite 6 | Fast HMR, Tauri plugin support | +| State | Zustand | Lightweight, no boilerplate, works with Tauri events | +| Routing | React Router v7 | File-based routes, type-safe | +| UI Components | shadcn/ui + Tailwind CSS | Accessible, customizable, no runtime CSS-in-JS | +| Charts | Recharts or visx | Real-time signal visualization | +| Topology Graph | D3 force-directed | Mesh network visualization | +| Serial UI | Custom | Tauri command integration | +| Icons | Lucide React | Consistent, tree-shakeable | + +#### 3.2 Page Layout + +``` ++------------------------------------------+ +| RuView [Settings] [?] | ++-------+----------------------------------+ +| | | +| Nav | Dashboard / Active Page | +| | | +| [D] | +--------+ +--------+ +------+ | +| [F] | | Node 1 | | Node 2 | | +Add | | +| [W] | +--------+ +--------+ +------+ | +| [S] | | +| [M] | Server Status: Running | +| [T] | +--------------------------+ | +| | | Live Signal / Pose View | | +| | +--------------------------+ | ++-------+----------------------------------+ +| Status Bar: 3 nodes | Server: :8080 | ++------------------------------------------+ + +Nav items: + [D] Dashboard — overview of all nodes and server + [F] Flash — firmware flashing wizard + [W] WASM — edge module management + [S] Sensing — live sensing data view + [M] Mesh — topology visualization + [T] Settings — ports, paths, preferences +``` + +#### 3.3 Dashboard Page + +The dashboard is the primary landing page showing: + +1. **Node Grid** — cards for each discovered ESP32 node showing: + - IP address and hostname + - Firmware version (with update indicator if newer available) + - Node ID and TDM slot assignment + - Edge processing tier (raw / stats / vitals) + - Signal quality indicator (last CSI frame age) + - Health status (online/offline/degraded) + - Quick actions: OTA update, configure, view logs + +2. **Sensing Server Panel** — start/stop button, port configuration, log tail + +3. **Discovery Controls** — scan button, auto-discovery toggle, network range filter + +#### 3.4 Flash Firmware Page + +A wizard-style flow: + +1. **Select Port** — dropdown of detected serial ports with chip info +2. **Select Firmware** — file picker for `.bin` files, or select from bundled builds +3. **Configure** — chip type, baud rate, flash mode +4. **Flash** — progress bar with phase indicators (connecting, erasing, writing, verifying) +5. **Provision** — optional NVS provisioning form (WiFi, target IP, TDM, edge tier) +6. **Verify** — serial monitor showing boot log, success/fail indicator + +#### 3.5 WASM Module Manager Page + +| Column | Content | +|--------|---------| +| Module ID | Auto-assigned by node | +| Name | Filename of uploaded `.wasm` | +| Size | Module size in KB | +| Status | Running / Stopped / Error | +| Node | Which ESP32 node it runs on | +| Actions | Start / Stop / Unload / View Logs | + +Upload panel: drag-and-drop `.wasm` file, select target node(s), upload button. + +#### 3.6 Sensing View Page + +Embeds the existing web UI (`ui/`) via an iframe pointing at the sensing server's static file route, or builds native React components that connect to the same WebSocket API. The native approach is preferred because it allows: + +- Tighter integration with the node status sidebar +- Shared state between hardware management and visualization +- Offline access to recorded data + +Key visualization components: +- **CSI Heatmap** — subcarrier amplitude over time +- **Signal Field** — 2D signal strength visualization +- **Pose Skeleton** — detected body keypoints and connections +- **Vital Signs** — real-time breathing rate and heart rate charts +- **Activity Classification** — current activity label with confidence + +#### 3.7 Mesh Topology Page + +A force-directed graph showing: +- Nodes as circles (color = health status, size = edge tier) +- Edges between nodes that can see each other +- TDM slot labels on each node +- Sync status indicators (in-sync / drifting / lost) +- Click a node to navigate to its detail page + +### 4. Platform-Specific Considerations + +#### 4.1 macOS + +- **Serial driver signing**: CP210x and CH340 drivers require user approval in System Preferences > Security +- **App signing**: Tauri apps must be signed and notarized for distribution outside the App Store +- **USB permissions**: No special permissions needed beyond driver installation +- **CoreWLAN**: The sensing server can use CoreWLAN for WiFi scanning (ADR-025); the desktop app inherits this capability + +#### 4.2 Windows + +- **COM port access**: Windows assigns COM port numbers; the app lists them via the Windows Registry or `SetupDi` API +- **Driver installation**: USB-to-serial drivers (CP210x, CH340, FTDI) must be installed; the app can detect missing drivers and link to downloads +- **Firewall**: The sensing server's UDP listener may trigger Windows Firewall prompts; the app should pre-configure rules or guide the user +- **Code signing**: EV certificate required for SmartScreen trust; unsigned apps trigger warnings + +#### 4.3 Linux + +- **udev rules**: ESP32 serial ports (`/dev/ttyUSB*`, `/dev/ttyACM*`) require udev rules for non-root access. The app bundles a `99-ruview-esp32.rules` file and offers to install it: + ``` + SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", MODE="0666" # CP210x + SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", MODE="0666" # CH340 + ``` +- **AppImage/deb/rpm**: Tauri supports all three packaging formats +- **Wayland vs X11**: Tauri uses webkit2gtk which works on both + +### 5. Cargo.toml for the Desktop Crate + +```toml +[package] +name = "wifi-densepose-desktop" +version.workspace = true +edition.workspace = true +description = "Tauri desktop frontend for RuView WiFi DensePose" +license.workspace = true +authors.workspace = true + +[lib] +name = "wifi_densepose_desktop" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-shell = "2" # Sidecar process management +tauri-plugin-dialog = "2" # File picker dialogs +tauri-plugin-fs = "2" # Filesystem access +tauri-plugin-process = "2" # Process management +tauri-plugin-notification = "2" # Desktop notifications + +# Workspace crates +wifi-densepose-hardware = { workspace = true } +wifi-densepose-config = { workspace = true } +wifi-densepose-core = { workspace = true } + +# Serial port access +serialport = { workspace = true } + +# ESP32 flashing (Rust-native, replaces esptool.py) +espflash = "3" + +# Network discovery +mdns-sd = "0.11" # mDNS/DNS-SD service discovery + +# HTTP client for OTA and WASM management +reqwest = { version = "0.12", features = ["json", "multipart", "stream"] } + +# Async runtime +tokio = { workspace = true } + +# Serialization +serde = { workspace = true } +serde_json = { workspace = true } + +# Logging +tracing = { workspace = true } +tracing-subscriber = { workspace = true } + +# Time +chrono = { version = "0.4", features = ["serde"] } +``` + +### 6. Tauri Configuration + +```json +{ + "$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-config-schema/schema.json", + "productName": "RuView", + "version": "0.3.0", + "identifier": "net.ruv.ruview", + "build": { + "frontendDist": "../frontend/dist", + "devUrl": "http://localhost:5173", + "beforeDevCommand": "cd frontend && npm run dev", + "beforeBuildCommand": "cd frontend && npm run build" + }, + "app": { + "windows": [ + { + "title": "RuView - WiFi DensePose", + "width": 1280, + "height": 800, + "minWidth": 900, + "minHeight": 600 + } + ] + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "externalBin": ["sensing-server"], + "linux": { + "deb": { "depends": ["libwebkit2gtk-4.1-0"] }, + "appimage": { "bundleMediaFramework": true } + }, + "windows": { + "wix": { "language": "en-US" } + } + } +} +``` + +### 7. Tauri v2 Capabilities (Permissions) + +```json +{ + "identifier": "default", + "description": "RuView default capability set", + "windows": ["main"], + "permissions": [ + "core:default", + "shell:allow-execute", + "shell:allow-open", + "dialog:allow-open", + "dialog:allow-save", + "fs:allow-read", + "fs:allow-write", + "process:allow-exit", + "notification:default" + ] +} +``` + +### 8. Development Workflow + +```bash +# Prerequisites +cargo install tauri-cli@^2 +cd rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop/frontend +npm install + +# Development (hot-reload frontend + Rust rebuild) +cd rust-port/wifi-densepose-rs/crates/wifi-densepose-desktop +cargo tauri dev + +# Production build +cargo tauri build + +# Build sensing-server sidecar (must be done before tauri build) +cargo build --release -p wifi-densepose-sensing-server +# Copy to sidecar location: +# target/release/sensing-server -> crates/wifi-densepose-desktop/binaries/sensing-server-{arch} +``` + +### 9. Security Considerations + +1. **PSK Storage**: OTA PSK tokens are stored in the OS keychain via `tauri-plugin-stronghold` or the platform's native credential store, never in plaintext config files. + +2. **Serial Port Access**: Tauri's capability system restricts which commands the frontend can invoke. Serial port access is only available through the typed `flash_firmware` and `provision_node` commands, not raw serial I/O. + +3. **Network Requests**: OTA and WASM management commands only communicate with nodes on the local network. The app does not make external network requests except for update checks (opt-in). + +4. **Firmware Validation**: Before flashing, the app validates the firmware binary header (ESP32 image magic bytes, partition table offset) to prevent bricking. + +5. **WASM Signature Verification**: The desktop app can sign WASM modules before upload using a locally stored Ed25519 key pair, complementing the node-side verification (ADR-040). + +### 10. Implementation Phases + +| Phase | Scope | Effort | Priority | +|-------|-------|--------|----------| +| **Phase 1: Skeleton** | Tauri project scaffolding, workspace integration, basic window with React | 1 week | P0 | +| **Phase 2: Discovery** | Serial port listing, UDP/mDNS node discovery, dashboard with node cards | 1 week | P0 | +| **Phase 3: Flash** | espflash integration, firmware flashing wizard with progress events | 1 week | P0 | +| **Phase 4: Server** | Sidecar sensing server start/stop, log viewer, status panel | 1 week | P1 | +| **Phase 5: OTA** | HTTP OTA with PSK auth, batch update, version comparison | 1 week | P1 | +| **Phase 6: Provisioning** | NVS read/write via serial, provisioning form, mesh config file | 1 week | P1 | +| **Phase 7: WASM** | Module upload/list/start/stop, drag-and-drop, per-module logs | 1 week | P2 | +| **Phase 8: Sensing** | WebSocket integration, live signal charts, pose overlay | 2 weeks | P2 | +| **Phase 9: Mesh View** | Force-directed topology graph, TDM slot visualization, sync status | 1 week | P2 | +| **Phase 10: Polish** | App signing, auto-update, udev rules installer, onboarding wizard | 1 week | P3 | + +Total estimated effort: ~11 weeks for a single developer. + +## Consequences + +### Positive + +- **Single pane of glass** — all hardware management, sensing, and visualization in one app +- **No Python dependency** — Rust-native `espflash` replaces `esptool.py` for firmware flashing +- **Replaces 6+ CLI tools** — flash, provision, OTA, WASM management, server control, visualization +- **Accessible to non-developers** — GUI replaces CLI flags and curl commands +- **Cross-platform** — one codebase for Windows, macOS, Linux +- **Workspace integration** — shares types, config, and hardware crates with sensing server +- **Small binary** — ~15-20 MB vs ~150 MB for Electron equivalent + +### Negative + +- **New frontend dependency** — introduces Node.js/npm build step into the Rust workspace +- **Tauri version churn** — Tauri v2 is recent; API stability is not yet proven at scale +- **webkit2gtk on Linux** — depends on system webview version; old distros may have stale webkit +- **espflash limitations** — the `espflash` crate may not support all chip variants or flash modes that `esptool.py` handles; fallback to bundled Python is needed +- **Maintenance surface** — adds ~5,000 lines of TypeScript and ~2,000 lines of Rust + +### Risks + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|------------| +| espflash cannot flash all ESP32 variants | Medium | High | Bundle esptool.py as fallback sidecar | +| Tauri v2 breaking changes | Low | Medium | Pin to specific Tauri version; update in dedicated PRs | +| Serial port access fails on macOS Sequoia+ | Medium | Medium | Test on latest macOS; document driver requirements | +| webkit2gtk version mismatch on Linux | Medium | Low | Set minimum version in deb/rpm dependencies | +| Sidecar sensing server fails to start | Low | Medium | Detect failure and show manual start instructions | + +## References + +- Tauri v2 documentation: https://v2.tauri.app/ +- espflash crate: https://crates.io/crates/espflash +- mdns-sd crate: https://crates.io/crates/mdns-sd +- ADR-012: ESP32 CSI Sensor Mesh +- ADR-039: ESP32 Edge Intelligence +- ADR-040: WASM Programmable Sensing +- ADR-044: Provisioning Tool Enhancements +- ADR-050: Quality Engineering — Security Hardening +- ADR-051: Sensing Server Decomposition +- `firmware/esp32-csi-node/` — ESP32 firmware source +- `firmware/esp32-csi-node/provision.py` — Current provisioning script +- `rust-port/wifi-densepose-rs/crates/wifi-densepose-sensing-server/` — Sensing server +- `rust-port/wifi-densepose-rs/crates/wifi-densepose-hardware/` — Hardware crate +- `ui/` — Existing web UI