Moveet: incidents, recording & replay, icon rail UI, and 500+ tests
Source: Dev.to
What shipped
🚦 Fleet management
Vehicles can now be grouped into named, colour‑coded fleets. You create a fleet, assign vehicles to it, and the UI colours routes and markers per fleet – handy for simulating multiple operators sharing the same road network.
REST API
POST /fleets
GET /fleets
DELETE /fleets/:id
POST /fleets/:id/assign
POST /fleets/:id/unassignWebSocket events
fleet:createdfleet:deletedfleet:assigned
⚠️ Incidents and dynamic rerouting
Drop a road incident anywhere on the network. Every vehicle currently routing through the affected segment is rerouted live – A* runs again from the vehicle’s current position, routing around the blocked edge.
REST API
POST /incidents # create incident, triggers rerouting
GET /incidents # list active incidents
DELETE /incidents/:id # clear it, vehicles return to normal paths
POST /incidents/random # random incident for testingWebSocket event
vehicle:rerouted– emitted for each vehicle that is rerouted
Incident markers appear on the map.
🎬 Session recording and replay
Every simulation session can be recorded to a timestamped NDJSON file. The format is a header line followed by one event per line (direction assignments, vehicle snapshots, incidents, …).
REST API
POST /recording/start
POST /recording/stop
GET /recordingsReplay control API
POST /replay/start { "file": "path/to/recording.ndjson" }
POST /replay/pause
POST /replay/resume
POST /replay/stop
POST /replay/seek { "timestamp": 12000 }
POST /replay/speed { "speed": 2 }
GET /replay/statusPlayback supports 1×, 2×, and 4× speed. The UI shows an interpolated progress bar that advances smoothly between server ticks, avoiding jumps every 500 ms.
🖥 UI redesign – icon rail + panel sidebar
The old floating overlay has been replaced by an icon rail on the left edge – a vertical strip of icon buttons that each toggle a panel.
| Icon | Panel |
|---|---|
| 🚗 | Vehicles – list, filter, select |
| 🗂 | Fleets – create, assign, colour |
| ⚠️ | Incidents – active list with badge count |
| ⏺ | Recordings – start/stop/browse/replay |
| 👁 | Visibility – toggle map layers |
| ⚡ | Speed – simulation speed controls |
| ⚙️ | Adapter – hot‑swap source/sink plugins |
The bottom dock holds live‑simulation controls (play/stop/reset/record) and collapses into a replay transport bar during playback.
All panel components are built from a shared set of primitives (PanelShell, PanelHeader, PanelSection) and a unified theme token set, so every panel looks consistent without per‑component ad‑hoc styling.
✅ Test coverage – from 410 to 502 tests
The simulator test suite grew significantly. New files added:
| File | What it covers |
|---|---|
rateLimiter.test.ts | Window enforcement, 429 responses, per‑IP tracking, cleanup interval |
helpers.test.ts | calculateBearing, interpolatePosition, calculateDistance, nonCircularRouteEdges, estimateRouteDuration |
serializer.test.ts | serializeVehicle with and without fleet assignment |
config.test.ts | verifyConfig – missing file, port range, speed ordering, all numeric constraints |
SimulationController.test.ts | Full lifecycle: start, stop, getStatus, setOptions, getVehicles, getInterval, all replay methods |
Testing pattern worth noting – SimulationController tests stub VehicleManager.prototype.setRandomDestination to prevent A* calls on the tiny test‑network fixture, then restore it. Replay tests write a minimal NDJSON file to a temporary directory before each test, and everything is cleaned up in afterEach.
📡 WebSocket – 100 ms batching + back‑pressure
Vehicle position updates are now batched every 100 ms before broadcast. If a client’s write buffer is backed up, the broadcaster skips that client rather than growing an unbounded queue. This keeps the simulator usable with 50–100 vehicles without connection degradation.
All broadcast event types
vehicles – batched position array (100 ms window)
status – simulation state (running / ready / interval)
options – current StartOptions
heatzones – HeatZoneFeature[]
direction – active dispatch assignment
waypoint:reached – vehicle reached a waypoint
route:completed – vehicle completed its full route
reset – simulation was reset
fleet:created / fleet:deleted / fleet:assigned
incident:created / incident:cleared
vehicle:rerouted – live A* reroute triggered🔧 CI improvements
The GitHub Actions workflow previously ran npm ci three times in parallel (once per job). It now has a dedicated setup job that installs once and caches node_modules keyed on package-lock.json. Lint, test, and build jobs restore from that cache. The build job also caches .turbo/ with a per‑commit key and branch‑level restore keys.
Architecture diagram (updated)
┌──────────────────────────────────────┐
│ apps/ui │
│ React 19 · D3 7 · Vite · TS 5.8 │
│ Mercator SVG map, 1×–15× zoom │
│ Icon rail · Panel sidebar · Dock │
└──────────────┬───────────────────────┘Architecture Overview
│ REST + WebSocket
▼
┌──────────────────────────────────────┐
│ apps/simulator │
│ Express 4 · ws 8 · Turf.js 7 │
│ GeoJSON graph · A* · LRU cache │
│ WS broadcaster · 100ms batching │
└──────────────┬───────────────────────┘
│ GET /vehicles · POST /sync
▼
┌──────────────────────────────────────┐
│ apps/adapter (optional) │
│ Source plugins: static / graphql / │
│ rest / mysql / postgres │
│ Sink plugins: console / graphql / │
│ rest / redpanda / redis / webhook │
└──────────────────────────────────────┘Quick start
curl -O https://raw.githubusercontent.com/ivannovazzi/moveet/main/docker-compose.ghcr.yml
docker compose -f docker-compose.ghcr.yml upOpen . No configuration, no API keys required.
Repository:
Feel free to ask questions about the A implementation, the D3 renderer, or the recording format in the comments.*