# Migration Guide: WPF → Flutter (No AppServer)

> This guide maps every component of the legacy WPF COPILOT D6 system to its Flutter equivalent. The key architectural change: **there is no centralized AppServer** — the PRIMARY keyboard handles coordination.

## Architecture Transformation

```mermaid
graph TB
    subgraph "LEGACY Architecture"
        direction TB
        L_HW["Hardware Keyboard"] --> L_App["Copilot.App.exe<br/>(WPF .NET 7)"]
        L_App -->|"Native SDK DLL<br/>(in-process)"| L_Cam["Camera Servers"]
        L_App -->|"SignalR<br/>(HTTPS/WSS)"| L_AS["AppServer<br/>(separate machine)"]
        L_AS --> L_DB["SQLite DB"]
    end

    subgraph "NEW Architecture"
        direction TB
        N_HW["Hardware Keyboard"] --> N_App["Flutter App<br/>(Web/Desktop)"]
        N_App -->|"REST HTTP<br/>(localhost bridges)"| N_Bridge["C# Bridges<br/>(.NET 8)"]
        N_Bridge -->|"Native SDK"| N_Cam["Camera Servers"]
        N_App -->|"WebSocket<br/>(:8090)"| N_Primary["PRIMARY Keyboard<br/>(coordination)"]
    end
```

## What Replaces the AppServer?

The legacy AppServer was a centralized ASP.NET Core server providing:

| AppServer Feature | New Approach | Where It Runs |
|-------------------|-------------|---------------|
| Camera locks (SignalR hub) | WebSocket hub + in-memory state | PRIMARY keyboard |
| Sequence execution | Sequence runner service | PRIMARY keyboard |
| Config file sync | Local JSON files + PRIMARY broadcast | Each keyboard |
| Alarm history (Camea API) | Bridge alarm endpoints + periodic sync | Each keyboard queries bridges |
| Viewer state tracking | Bridge WebSocket events | Each keyboard tracks locally |
| Auto-update | Out of scope (Phase 4+) | — |
| Admin UI (Blazor) | Not needed initially | — |

### PRIMARY/STANDBY Model

```mermaid
sequenceDiagram
    participant K1 as Keyboard 1 (PRIMARY)
    participant K2 as Keyboard 2 (STANDBY)
    participant B as C# Bridges
    participant C as Camera Servers

    Note over K1,K2: Normal operation
    K1->>B: Direct commands (CrossSwitch, PTZ)
    K2->>B: Direct commands (CrossSwitch, PTZ)
    K1->>K2: Heartbeat every 2s via WebSocket
    K1->>K2: State sync (locks, sequences)

    Note over K1,K2: PRIMARY failure
    K2->>K2: No heartbeat for 6s
    K2->>K2: Self-promote to PRIMARY
    K2->>B: Continue direct commands
    Note over K2: Lock/sequence state rebuilt from bridges
```

**Key principle:** Direct commands (CrossSwitch, PTZ) **always work** — they go straight to bridges. Only coordination features (locks, sequences) need the PRIMARY.

## Component Mapping: Legacy → Flutter

### Assembly-Level Mapping

| Legacy Assembly | Flutter Equivalent | Status |
|----------------|-------------------|--------|
| `Copilot.App.dll` (WPF UI + ViewModels) | `copilot_keyboard/` Flutter app | Partially built |
| `Copilot.Device.dll` (Serial + HID) | Web Serial API / `flutter_libserialport` | Not started |
| `Copilot.Drivers.GeViScope.dll` | GeViScope Bridge (:7720) REST API | **Complete** |
| `Copilot.Drivers.GCore.dll` | G-Core Bridge (:7721) REST API | **Complete** |
| `Copilot.Drivers.GeviSoft.dll` | GeViServer Bridge (:7710) REST API | Minimal |
| `Copilot.Drivers.Common.dll` | Bridge REST API contracts | **Complete** |
| `Copilot.Common.dll` | `config/`, `domain/entities/` | Partially built |
| `Copilot.Common.Services.dll` | `data/services/` (BridgeService, StateService) | Partially built |
| `Copilot.AppServer.Client.dll` | `CoordinationService` (WebSocket to PRIMARY) | Not started |
| `Copilot.AppServer.dll` | PRIMARY keyboard coordination logic | Not started |
| `Copilot.AppServer.Database.dll` | In-memory state on PRIMARY | Not started |

### Class-Level Mapping

| Legacy Class | Flutter Class | BLoC | Notes |
|-------------|--------------|------|-------|
| `SegmentViewModel` (1323 lines) | Split into multiple BLoCs | `WallBloc` + `PtzBloc` + `CameraBloc` | Core logic, needs decomposition |
| `MainWindow` (217 lines) | `MainScreen` + `KeyboardService` | — | Input routing |
| `CameraControllerService` (65 lines) | `BridgeService` | — | Already mapped to REST |
| `CameraLockService` (61 lines) | `LockService` (new) | `LockBloc` (new) | Via PRIMARY WebSocket |
| `SequenceService` (77 lines) | `SequenceService` (new) | `SequenceBloc` (new) | Via PRIMARY WebSocket |
| `ConfigurationService` (100 lines) | `AppConfig` + JSON files | — | Local files, no sync |
| `FunctionButtonsService` (78 lines) | `FunctionButtonService` (new) | `WallBloc` | Config-driven |
| `PrepositionService` (114 lines) | `PrepositionService` (new) | `PrepositionBloc` (new) | Via bridge REST |
| `CameraAlarmService` (47 lines) | `AlarmService` (exists) | `AlarmBloc` (exists) | Already implemented |
| `PlaybackStateService` (37 lines) | `PlaybackService` (new) | `PlaybackBloc` (new) | Via bridge REST |
| `CopilotDevice` | `KeyboardService` (new) | — | Web Serial + HID APIs |
| `NavigationService` | `GoRouter` | — | Already in pubspec |

## What's Already Built vs. What's Missing

### Already Built in Flutter

| Feature | Files | Status |
|---------|-------|--------|
| CrossSwitch (camera → monitor) | `WallBloc`, `BridgeService` | Working |
| PTZ control (Pan/Tilt/Zoom) | `PtzBloc`, `BridgeService` | Working |
| Camera number input (numpad) | `WallBloc`, `MainScreen` | Working |
| Camera prefix selection (500/501/502) | `WallBloc` | Working |
| Monitor state tracking | `MonitorBloc`, `StateService` | Working |
| Alarm monitoring | `AlarmBloc`, `AlarmService` | Working |
| Connection management | `ConnectionBloc`, `BridgeService` | Working |
| Wall grid UI (5 sections) | `WallOverview`, `SectionView` | Working |
| WebSocket event streaming | `BridgeService` | Working |
| Bridge health checks | `BridgeService` | Working |
| DI container (GetIt) | `injection_container.dart` | Working |

### Missing — Must Be Built

| Feature | Priority | Complexity | Legacy Reference |
|---------|----------|-----------|------------------|
| Camera lock system | **Critical** | High | `CameraLockService`, `SegmentViewModel:631-676` |
| PRIMARY/STANDBY coordination | **Critical** | High | Replaces `AppServer` |
| Hardware keyboard (Serial) | **Critical** | Medium | `CopilotDevice`, `SerialPortDataProvider` |
| Hardware joystick (HID) | **Critical** | Medium | `JoystickHidDataProvider` |
| Function buttons (F1-F7) | High | Low | `FunctionButtonsService` |
| Prepositions (saved positions) | High | Medium | `PrepositionService` |
| Camera number edit timeout | High | Low | `SegmentViewModel:829-847` |
| Playback mode (jog/shuttle) | Medium | Medium | `PlaybackStateService`, viewer controller |
| Sequences (camera cycling) | Medium | High | `SequenceService` |
| Keyboard emulation (dev mode) | Medium | Low | `MainWindow:60-104` |
| Service menu | Low | Low | Long-press Backspace 3s |
| Config hot-reload | Low | Medium | `ConfigurationService` |
| CrossSwitch rules engine | Medium | Medium | `crossswitch-rules.json` |
| Alarm history view | Low | Low | `CameraAlarmService.GetAlarmForCamera` |

## Implementation Phases

### Phase 2A: Camera Lock System (Critical)

The lock system is the most complex missing piece. In the legacy app:

1. User selects a PTZ camera → triggers lock attempt
2. If lock acquired → PTZ telemetry enabled
3. If locked by another keyboard → dialog: "Request takeover?"
4. Lock expires after 5 minutes unless reset by PTZ movement
5. 1-minute warning before expiration
6. Priority levels: Low (default), High (override)

**New implementation:**
- PRIMARY keyboard manages lock state in memory
- Keyboards send lock requests via WebSocket to PRIMARY
- PRIMARY broadcasts lock state changes to all keyboards
- In degraded mode (no PRIMARY): local-only locks (no coordination)

### Phase 2B: PRIMARY/STANDBY Coordination

```
WebSocket Hub on PRIMARY (:8090)
├── /ws/heartbeat     → 2-second heartbeat
├── /ws/locks         → Lock state sync
├── /ws/sequences     → Sequence state sync
└── /ws/state         → General state broadcast
```

### Phase 3: Hardware Input

Serial port protocol (from `CopilotDevice`):
- Prefix `p` = key press, `r` = key release
- Prefix `j` = jog value, `s` = shuttle value
- Prefix `v` = version, `h` = heartbeat

HID joystick:
- VendorId: 10959 (0x2ACF), ProductId: 257 (0x0101)
- 3 axes: X (pan), Y (tilt), Z (zoom)
- Value range: -255 to +255

### Phase 4: Sequences & Playback

- Sequences run on PRIMARY keyboard (not AppServer)
- Playback via bridge viewer control endpoints
- Jog/shuttle wheel maps to playback speed (-7 to +7)

## Bridge API Reference (Quick)

Both bridges expose identical endpoints:

```
POST /viewer/connect-live   {Viewer, Channel}         → CrossSwitch
POST /viewer/clear          {Viewer}                   → Clear monitor
POST /camera/pan            {Camera, Direction, Speed}  → PTZ pan
POST /camera/tilt           {Camera, Direction, Speed}  → PTZ tilt
POST /camera/zoom           {Camera, Direction, Speed}  → PTZ zoom
POST /camera/stop           {Camera}                    → Stop PTZ
POST /camera/preset         {Camera, Preset}            → Go to preset
GET  /monitors                                          → All monitor states
GET  /alarms/active                                     → Active alarms
GET  /health                                            → Bridge health
WS   /ws/events                                         → Real-time events
```

## Configuration Files

| File | Purpose | Legacy Equivalent |
|------|---------|-------------------|
| `servers.json` | Server connections, bridge URLs, ID ranges | `CameraServersConfiguration` |
| `keyboards.json` | Keyboard identity, PRIMARY/STANDBY role | `CopilotAppConfiguration` |
| `wall-config.json` | Monitor wall layout (sections, monitors, viewers) | `MonitorWallConfiguration` |
| `function-buttons.json` | F1-F7 actions per wall | `FunctionButtonsConfiguration` |
| `prepositions.json` | Camera preset names | `PrepositionsConfiguration` |
| `crossswitch-rules.json` | Routing rules for CrossSwitch | New (was in AppServer logic) |

## Risk Mitigation

| Risk | Mitigation |
|------|-----------|
| Joystick latency via HTTP bridge | Measure. Legacy: ~1ms (in-process). New: ~5-20ms (HTTP). Acceptable for PTZ. |
| Lock coordination without AppServer | PRIMARY WebSocket hub. Degraded mode = local-only locks. |
| Sequence failover mid-execution | PRIMARY tracks sequence state. STANDBY can resume from last known position. |
| Config drift between keyboards | PRIMARY broadcasts config changes. Startup sync on connect. |
| Browser HID/Serial support | Use Web HID API + Web Serial API. Desktop fallback via `flutter_libserialport`. |
