From 02fa6e4d4fc6a425b3840eecd6cea2f7e08fcd0f Mon Sep 17 00:00:00 2001 From: Otto the Minion Date: Thu, 21 May 2026 13:19:26 -0400 Subject: [PATCH] docs: add comprehensive project context file (CONTEXT.md) for agent reference --- docs/CONTEXT.md | 322 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 docs/CONTEXT.md diff --git a/docs/CONTEXT.md b/docs/CONTEXT.md new file mode 100644 index 0000000..19ceeb3 --- /dev/null +++ b/docs/CONTEXT.md @@ -0,0 +1,322 @@ +# RemoteRig — Project Context + +> **Last updated:** 2026-05-21 +> **Repo:** `CubeCraft-Creations/remote-rig` | **Host:** `code.cubecraftcreations.com` +> **Local clone:** `/mnt/ai-storage/projects/remote-rig` | **Default branch:** `dev` +> **Discord:** `DISCORD_DEV_REMOTERIG_CHANNEL_ID` +> **Linear Epic:** [CUB-198](https://linear.app/cubecraft-creations/issue/CUB-198) + +--- + +## Overview + +RemoteRig is a **multi-camera remote monitoring system**. It provides a camera grid dashboard for monitoring multiple GoPro cameras remotely. Cameras push status via MQTT/HTTP, the UI shows a live grid with SSE updates, and the system supports start/stop recording control. + +**Target hardware:** Raspberry Pi Zero 2 W as the central hub, with ESP32 nodes attached to each GoPro camera for status collection and MQTT communication. + +## Tech Stack + +| Layer | Technology | Notes | +|-------|-----------|-------| +| Backend | Go 1.25+ | Chi v5 router, SQLite (modernc.org/sqlite), go-yaml v3 | +| Frontend | React 19 + TypeScript 5.7 | Vite 6, Tailwind CSS 3.4 | +| State | Zustand 5 | Client-side camera state store | +| Icons | lucide-react 0.469 | | +| Real-time | SSE (Server-Sent Events) | `/api/v1/events/stream` | +| Messaging | MQTT | Mosquitto broker for ESP32 → hub communication | +| Database | SQLite | WAL mode, foreign keys enabled | +| Auth | API key (Bearer token) | Middleware, configurable in `config.yaml` | +| Testing | Vitest 4.1 + testing-library/react | Unit tests for React components | +| Linting | ESLint 9 | `npm run lint` | +| CI/CD | Gitea Actions | `.gitea/workflows/ci.yaml`, `build-dev.yaml`, `deploy-dev.yaml` | + +## Architecture + +``` +ESP32 Node (per camera) + │ + ├── HTTP POST /api/v1/cameras/:id/status (status push) + │ + └── MQTT ──→ Mosquitto Broker ──→ MQTT Subscriber (Go hub) + │ + ▼ + ┌──────────────────────────────┐ + │ Go Central Hub (Pi Zero 2 W)│ + │ ┌────────────────────┐ │ + │ │ SQLite Database │ │ + │ └────────────────────┘ │ + │ ┌────────────────────┐ │ + │ │ SSE Hub │ │ + │ └────────────────────┘ │ + └──────────────────────────────┘ + │ + SSE /api/v1/events/stream + │ + ▼ + React Dashboard (Browser) +``` + +### Key Architecture Decisions +- **SQLite over PostgreSQL:** Single-node deployment on Pi Zero 2 W — no need for separate DB server +- **SSE over WebSocket:** Simpler server-side; unidirectional updates from hub → browser are sufficient +- **MQTT for ESP32 → Hub communication:** Lightweight, designed for IoT/embedded use +- **API key auth:** Simple bearer token middleware; configurable key in `config.yaml` +- **Idempotent migrations:** DB checks if tables exist before running migrations + +## Directory Layout + +``` +remote-rig/ +├── cmd/server/main.go # Entry point — config load, router setup, graceful shutdown +├── config.yaml # Runtime configuration +├── go.mod / go.sum # Go dependencies +├── internal/ +│ ├── api/ +│ │ ├── api.go # Package doc +│ │ ├── cameras.go # GET /cameras, POST /cameras, GET /cameras/:id +│ │ ├── recording.go # POST /cameras/:id/start, POST /cameras/:id/stop +│ │ └── status.go # POST /cameras/:id/status (push from ESP32) +│ ├── auth/ +│ │ └── middleware.go # API key auth middleware +│ ├── db/ +│ │ ├── db.go # Open, migrations, WAL mode +│ │ └── migrations/ +│ │ └── 001_create_tables.sql +│ └── events/ +│ └── sse.go # SSE hub (subscribe, broadcast) +├── pkg/models/ +│ └── camera.go # Camera, StatusLog, RecordingEvent, CameraStatus, Settings +├── src/ # React frontend +│ ├── App.tsx # Main app — header, camera grid, footer +│ ├── components/ +│ │ ├── CameraCard.tsx # Single camera status card +│ │ ├── CameraCard.test.tsx # Unit tests +│ │ └── index.ts +│ ├── hooks/ +│ │ ├── useSSE.ts # SSE connection hook +│ │ ├── useCameraStatus.ts # Camera status hook +│ │ ├── useSystemHealth.ts # System health hook +│ │ └── index.ts +│ ├── services/ +│ │ └── api.ts # API client +│ ├── store/ +│ │ ├── useCameraStore.ts # Zustand store +│ │ └── index.ts +│ ├── types/ +│ │ └── index.ts # TypeScript interfaces +│ ├── utils/ +│ │ └── index.ts +│ └── main.tsx +├── docs/ +│ ├── CONTEXT.md # ← this file +│ └── plans/ +│ └── 2026-05-21-cub-196-cameracard.md # CameraCard implementation plan +├── .gitea/workflows/ +│ ├── ci.yaml # PR CI: lint → typecheck → test → build +│ ├── build-dev.yaml # Go binary build on dev push +│ └── deploy-dev.yaml # SCP + SSH deploy with rollback +├── .env.example # VITE_API_URL=http://localhost:8080/api +├── package.json +├── vite.config.ts +├── tailwind.config.js +└── tsconfig.json +``` + +## Database Schema (SQLite) + +### cameras +| Column | Type | Notes | +|--------|------|-------| +| camera_id | TEXT PK | Unique camera identifier | +| friendly_name | TEXT NOT NULL | Human-readable name | +| mac_address | TEXT UNIQUE | MAC address (optional) | +| created_at | DATETIME | Default now | +| updated_at | DATETIME | Default now | + +### status_logs +| Column | Type | Notes | +|--------|------|-------| +| id | INTEGER PK AUTO | | +| camera_id | TEXT FK → cameras | | +| recorded_at | DATETIME | Default now | +| battery_pct | INTEGER | Nullable | +| video_remaining_sec | INTEGER | Nullable | +| recording_state | INTEGER | 0=idle, 1=recording | +| mode | TEXT | e.g. "video" | +| resolution | TEXT | e.g. "1080p" | +| fps | INTEGER | | +| online | INTEGER | 0=offline, 1=online | +| raw_battery_pct | REAL | Float precision | + +Index: `(camera_id, recorded_at DESC)` + +### recording_events +| Column | Type | Notes | +|--------|------|-------| +| id | INTEGER PK AUTO | | +| camera_id | TEXT FK → cameras | | +| started_at | DATETIME NOT NULL | | +| stopped_at | DATETIME | Null while recording | +| reason | TEXT | e.g. "manual" | +| duration | INTEGER | Seconds | + +Index: `(camera_id, started_at DESC)` + +### settings +| Column | Type | Notes | +|--------|------|-------| +| key | TEXT PK | | +| value | TEXT NOT NULL | | +| updated_at | DATETIME | Default now | + +**Default seeds:** `poll_interval_sec=30`, `low_battery_threshold=15`, `low_storage_alert_sec=300` + +## API Endpoints + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | /health | No | Health check → `{"status":"ok"}` | +| GET | /api/v1/cameras | Yes | List all cameras with latest status | +| POST | /api/v1/cameras | Yes | Register a new camera | +| GET | /api/v1/cameras/:id | Yes | Camera detail + latest status + 24h history | +| POST | /api/v1/cameras/:id/start | Yes | Start recording + MQTT publish | +| POST | /api/v1/cameras/:id/stop | Yes | Stop recording | +| POST | /api/v1/cameras/:id/status | Yes | Push status from ESP32 node | +| GET | /api/v1/events/stream | No (SSE) | Real-time camera status stream | + +## Configuration (`config.yaml`) + +```yaml +db_path: "remoterig.db" # SQLite database path +api_key: "changeme" # Bearer token for API auth +port: 8080 # HTTP listen port +read_timeout: 5s +write_timeout: 10s +idle_timeout: 120s +mqtt: + broker: "localhost:1883" + client_id: "remoterig-hub" +platform: + type: "pi-zero-2w" + max_cameras: 16 +``` + +## Frontend Component Tree + +``` +App +├── Header +│ ├── Logo + Title ("RemoteRig Dashboard") +│ └── Stats bar (online count, recording count) +├── CameraGrid +│ └── CameraCard[] (responsive: 1→2→3→4 columns) +│ ├── Camera name + online/offline badge +│ ├── Resolution + FPS display +│ ├── Recording indicator (pulsing dot + REC/IDLE badge) +│ ├── Battery bar (color-coded: green/yellow/red) +│ └── Footer (Live/Last seen + video remaining) +└── Footer + └── "RemoteRig v0.1.0 — Multi-Camera Remote Monitoring System" +``` + +**Empty state:** "Waiting for cameras..." with pulsing radio icon when no cameras connected. +**Offline state:** Camera card dimmed with dashed border, shows "Last seen Xm ago". + +## Color Palette (Tailwind — dark dashboard theme) + +Custom theme in `tailwind.config.js`: +- `rig-dark-900` (background), `rig-dark-800`, `rig-dark-700` (cards), etc. +- `rig-accent` (accent color) +- `rig-success` (green — battery ≥50%, online) +- `rig-warning` (yellow — battery 15-49%) +- `rig-danger` (red — battery <15%, offline, recording) + +## Linear Issue Map + +| CUB | Title | Status | +|-----|-------|--------| +| 198 | **Epic: Multi-camera remote monitoring system** | Backlog | +| 178 | UX/UI design: camera monitoring dashboard mockups | Backlog | +| 179 | Logging & persistence strategy | In Review | +| 180 | Risk mitigation checklist | Done | +| 181 | Scaffold Go module, directory layout, config, main.go | Done | +| 182 | Scaffold Go module (dup of 181) | Backlog | +| 183 | SQLite schema migration + DB init | Backlog | +| 184 | API key auth middleware | Backlog | +| 185 | Camera/StatusLog/RecordingEvent Go models | Backlog | +| 186 | GET /api/v1/cameras (list with live status) | Backlog | +| 187 | POST /api/v1/cameras/:id/start + MQTT publish | Done | +| 188 | POST /api/v1/cameras/:id/stop recording | Backlog | +| 189 | POST /api/v1/cameras (register new camera) | Backlog | +| 190 | GET /api/v1/cameras/:id (detail + history) | Backlog | +| 191 | POST /api/v1/cameras/:id/push-status (HTTP ingestion) | Backlog | +| 192 | MQTT subscriber for status ingestion + fan-out | Backlog | +| 193 | SSE /api/v1/events/stream endpoint | Backlog | +| 194 | Scaffold Vite + React + TypeScript + Tailwind project | Done | +| 195 | React SSE hook (useSSE.ts) | In Review | +| 196 | React CameraCard component with status display | Backlog | +| 197 | UX wireframe — main dashboard camera grid | In Review | + +## CI/CD Pipeline + +### ci.yaml (PR gate) +Runs on push/PR to `dev` and `main`: +1. `lint-and-typecheck`: npm ci → eslint → tsc --noEmit +2. `test` (needs lint): npm test (vitest) +3. `build` (needs test): npm run build → upload dist artifact + +### build-dev.yaml +Triggered by `repository_dispatch: dev-build-success`: +- Checks out, downloads artifact, builds Go binary + +### deploy-dev.yaml +Triggered by `workflow_dispatch`: +- SCP binary + deploy script to dev host +- Deploy with backup/rollback, systemctl restart +- Failure notification + +## Key Design Decisions + +1. **SQLite chosen over PostgreSQL** — Single-node Pi Zero 2 W deployment; no need for a separate DB server. WAL mode for concurrent read/write. +2. **SSE chosen over WebSocket** — Unidirectional updates (hub → browser) are sufficient for status dashboard; SSE is simpler to implement and maintain. +3. **Chi router** — Lightweight, idiomatic Go HTTP router with middleware support. +4. **Zustand over Redux** — Minimal boilerplate for the camera status store. +5. **API key auth** — Simple bearer token; hub is on a local network, not internet-facing. +6. **MQTT** — Standard IoT protocol for ESP32 communication; Mosquitto broker runs locally on the Pi. +7. **Recording state tracking** — Status push handler detects recording state changes and automatically opens/closes recording_events rows. + +## Known Limitations + +- MQTT subscriber implementation is still in Backlog (CUB-192). Currently, only HTTP status push is wired up. +- SSE endpoint implementation is in Backlog (CUB-193) — frontend SSE hook exists but backend SSE hub is placeholder. +- CameraCard component plan exists (`docs/plans/2026-05-21-cub-196-cameracard.md`) but implementation is pending. +- CUB-182 is essentially a duplicate of CUB-181 (both scaffold Go module). +- `remoterig.db` (SQLite DB file) is currently committed to the repo — should be in `.gitignore` for a production config. + +## Getting Started + +```bash +# Clone +cd /mnt/ai-storage/projects/remote-rig +git checkout dev +git pull origin dev + +# Backend +go run cmd/server/main.go +# → runs on :8080 + +# Frontend +cp .env.example .env # edit if needed +npm install +npm run dev # → Vite dev server with API proxy +``` + +## Default Agent Assignments + +| Area | Agent | Notes | +|------|-------|-------| +| Backend (Go API, MQTT, SSE) | Dex | gitea-dex MCP | +| Database (SQLite schema/migrations) | Hex | gitea-hex MCP | +| Frontend (React, Tailwind) | Rex | gitea-rex MCP | +| Hardware (ESP32 firmware, GPIO) | Pip | gitea-pip MCP | +| Design (wireframes, UX) | Sketch | |