Spaces:
Runtime error
Runtime error
Mayank commited on
Commit ·
1c94f12
1
Parent(s): 1514519
feat: Implement Telemetry Pipeline
Browse files- Frontend: Installed shadcn/ui and connected WebSocket dashboard
- Backend: Implemented rclnodejs gateway for /odom and /battery
- Simulation: Added mock_robot.py for data generation
- Scripts: Added dev-all.sh for parallel execution
- .agent/decisions/001-aliases-vs-scripts.md +29 -0
- .agent/decisions/002-ros-python-env.md +36 -0
- .agent/decisions/003-vertical-slicing.md +69 -0
- .agent/decisions/004-ui-library.md +39 -0
- .agent/how-to-think.md +61 -0
- .agent/project-memory.md +34 -0
- .agent/rules/how-to-think.md +155 -0
- .agent/rules/persona.md +27 -0
- .agent/rules/system-manifesto.md +135 -0
- .agent/workflows/build.md +55 -0
- .agent/workflows/deploy.md +26 -0
- .agent/workflows/dev-start.md +50 -0
- .agent/workflows/lint.md +27 -0
- .agent/workflows/ros2-launch.md +24 -0
- .agent/workflows/test.md +41 -0
- .agent/workflows/type-check.md +20 -0
- .gitignore +0 -6
- README.md +27 -26
- apps/backend/bun.lock +188 -0
- apps/backend/package.json +11 -0
- apps/backend/src/index.ts +31 -0
- apps/backend/src/modules/telemetry/telemetry.gateway.ts +105 -0
- apps/frontend/app/globals.css +199 -15
- apps/frontend/app/page.tsx +121 -57
- apps/frontend/bun.lock +56 -0
- apps/frontend/components.json +23 -0
- apps/frontend/components/layout/DashboardLayout.tsx +21 -0
- apps/frontend/components/layout/Sidebar.tsx +92 -0
- apps/frontend/components/ui/avatar.tsx +50 -0
- apps/frontend/components/ui/badge.tsx +36 -0
- apps/frontend/components/ui/button.tsx +57 -0
- apps/frontend/components/ui/card.tsx +76 -0
- apps/frontend/components/ui/table.tsx +120 -0
- apps/frontend/hooks/useFleetTelemetry.ts +65 -0
- apps/frontend/package.json +10 -1
- apps/frontend/tailwind.config.js +19 -0
- docs/planning/RFC-001-vertical-architecture.md +33 -0
- docs/planning/RFC-002-telemetry-streaming.md +62 -0
- robotics/ros2_ws/src/simulation_manager/src/mock_robot.py +90 -0
- scripts/dev-all.sh +41 -0
- system-index.json +30 -0
.agent/decisions/001-aliases-vs-scripts.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How to Think: Architectural Decisions - Aliases vs. Explicit Commands
|
| 2 |
+
|
| 3 |
+
## The Decision: Alias vs. Workflow
|
| 4 |
+
|
| 5 |
+
**Verdict**: We do **NOT** rely on local shell aliases for core project tasks.
|
| 6 |
+
**Reasoning**:
|
| 7 |
+
|
| 8 |
+
1. **Portability**: If I create an alias `run-all` in your `.bashrc`, it works today. If you switch machines or a new developer joins, the command vanishes. The project breaks.
|
| 9 |
+
2. **Transparency**: Hidden logic is dangerous. A command like `npm run build` is explicit and documented in `package.json`. An alias is a black box.
|
| 10 |
+
3. **CI/CD**: Your build server (GitHub Actions/GitLab CI) does not have your personal alias file. It needs standard commands.
|
| 11 |
+
|
| 12 |
+
## The Professional Alternative: Project Scripts
|
| 13 |
+
|
| 14 |
+
Instead of personal aliases, we use **Standardized Entry Points**.
|
| 15 |
+
|
| 16 |
+
| Task | Personal Alias (Bad) | Standard Script (Professional) |
|
| 17 |
+
| :--------------- | :------------------- | :--------------------------------------------- |
|
| 18 |
+
| Build Everything | `b` | `npm run build:all` (in root `package.json`) |
|
| 19 |
+
| Start Dev | `dev` | `npm run dev` (uses Turbo/Concurrently) |
|
| 20 |
+
| Launch ROS | `ros` | `ros2 launch ...` (Shell script in `scripts/`) |
|
| 21 |
+
|
| 22 |
+
## Exception: Personal Convenience
|
| 23 |
+
|
| 24 |
+
You (the user) are free to alias `g` to `git` or `k` to `kubectl` in your own shell profile. But **Antigravity (The Agent)** will always use the full, explicit command to ensure reproducibility and clarity in logs.
|
| 25 |
+
|
| 26 |
+
## Implemented Strategy
|
| 27 |
+
|
| 28 |
+
1. **Root `package.json`**: We will create a root `package.json` to act as the "Master Control" for the monorepo.
|
| 29 |
+
2. **Helper Scripts**: Complex logic (like "start 5 terminals") goes into `./scripts/dev.sh`, which is checked into Git.
|
.agent/decisions/002-ros-python-env.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architectural Decision: Python Environments in ROS 2
|
| 2 |
+
|
| 3 |
+
## The Conflict
|
| 4 |
+
|
| 5 |
+
You asked: _"Don't we need venv in robotics?"_
|
| 6 |
+
The short answer: **Usually, No.** (But with a catch).
|
| 7 |
+
|
| 8 |
+
## The Technical Reason (`rclpy`)
|
| 9 |
+
|
| 10 |
+
1. **Binary Bindings**: ROS 2 core libraries (like `rclpy`, `std_msgs`) are **C++ binaries** wrapped in Python. They are installed via `apt` into `/opt/ros/humble/lib/python3.10/site-packages`.
|
| 11 |
+
2. **The Venv Trap**: A standard Python `venv` is designed to be **isolated**. It explicitly _hides_ the system packages.
|
| 12 |
+
- Result: If you activate a venv and run a ROS node, it crashes with `ModuleNotFoundError: No module named 'rclpy'`.
|
| 13 |
+
3. **The Workaround**: You _can_ create a venv with `--system-site-packages`, but this defeats the purpose of isolation (you inherit all the system junk anyway).
|
| 14 |
+
|
| 15 |
+
## The Decision
|
| 16 |
+
|
| 17 |
+
We apply a **Split Strategy**:
|
| 18 |
+
|
| 19 |
+
| Layer | Environment | Strategy |
|
| 20 |
+
| :--------------------------------------- | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| 21 |
+
| **ML Service** (`apps/ml-service`) | **Strict `.venv`** | It's a standard Python web app. It needs precise versions of `torch`, `fastapi`, etc. It communicates with ROS via network/bridge, so it doesn't strictly need `rclpy` (unless it's a direct node). |
|
| 22 |
+
| **ROS 2 Workspace** (`robotics/ros2_ws`) | **System Python** | We rely on `rosdep` and the OS package manager (`apt`). We do **NOT** use a venv here. **Docker** provides the isolation layer for this part of the stack. |
|
| 23 |
+
|
| 24 |
+
## Why this is "Professional"
|
| 25 |
+
|
| 26 |
+
In production, your robot runs a **Docker Container**.
|
| 27 |
+
|
| 28 |
+
- Inside that container, there is only **one** python environment (the system one).
|
| 29 |
+
- Therefore, developing against the System Python in WSL mirrors the production Docker environment more closely than a synthetic venv.
|
| 30 |
+
|
| 31 |
+
## Exception
|
| 32 |
+
|
| 33 |
+
If you need a specific Python library (e.g., `requests`) for a ROS node:
|
| 34 |
+
|
| 35 |
+
1. Check if there is a ROS package for it: `sudo apt install python3-requests`.
|
| 36 |
+
2. If not, use `pip install --user` (be careful) or add it to `package.xml` and let `rosdep` handle it.
|
.agent/decisions/003-vertical-slicing.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architectural Decision: Vertical Feature Slicing
|
| 2 |
+
|
| 3 |
+
## The Problem
|
| 4 |
+
|
| 5 |
+
Monoliths (or even Monorepos) often suffer from "Horizontal Layering".
|
| 6 |
+
|
| 7 |
+
- **Horizontal**: "I will write all the database schemas. Then all the API routes. Then all the frontend components."
|
| 8 |
+
- **Result**: You have 3 broken layers for weeks. Nothing works until the very end.
|
| 9 |
+
|
| 10 |
+
## The Solution: Vertical Features
|
| 11 |
+
|
| 12 |
+
We build **End-to-End Features** one at a time. A feature is a vertical slice through the entire stack.
|
| 13 |
+
|
| 14 |
+
### 1. The Structure of a Vertical Slice
|
| 15 |
+
|
| 16 |
+
A single folder in your brain (not necessarily on disk, though we can group by domain) contains:
|
| 17 |
+
|
| 18 |
+
- **Database Schema** (Postgres Migration)
|
| 19 |
+
- **Backend Service** (API Route + Controller)
|
| 20 |
+
- **Shared Type** (DTO)
|
| 21 |
+
- **Frontend Component** (UI Widget)
|
| 22 |
+
- **ROS Message** (Topic Definition)
|
| 23 |
+
|
| 24 |
+
### 2. Feature-First Directory Structure (Future State)
|
| 25 |
+
|
| 26 |
+
Instead of:
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
/controllers
|
| 30 |
+
/auth_controller.ts
|
| 31 |
+
/robot_controller.ts
|
| 32 |
+
/models
|
| 33 |
+
/user.ts
|
| 34 |
+
/robot.ts
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
We move towards **Domain Modules** (in Backend):
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
/modules
|
| 41 |
+
/auth
|
| 42 |
+
/auth.controller.ts
|
| 43 |
+
/auth.service.ts
|
| 44 |
+
/auth.schema.ts
|
| 45 |
+
/robot-fleet
|
| 46 |
+
/fleet.controller.ts
|
| 47 |
+
/fleet.service.ts
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
### 3. The Coupling Rule
|
| 51 |
+
|
| 52 |
+
- **Loose Coupling Between Verticals**: The "Auth" module should not know about "Robot Path Planning". They talk via **Pub/Sub (Events)** or **Public Service Interface**.
|
| 53 |
+
- **Tight Coupling Within Verticals**: The "Robot Map" UI component requires the "Robot Map" API endpoint. They change together. This is good.
|
| 54 |
+
|
| 55 |
+
## Execution Strategy: "The Tracer Bullet"
|
| 56 |
+
|
| 57 |
+
For every new feature (e.g., "Add Lidar Visualization"):
|
| 58 |
+
|
| 59 |
+
1. **Define the Interface** (`packages/shared-types`): `LidarScan { ranges: number[] }`
|
| 60 |
+
2. **Implement ROS Node**: Publish `sensor_msgs/LaserScan`.
|
| 61 |
+
3. **Implement Backend Bridge**: Subscribe to ROS, forward to Websocket.
|
| 62 |
+
4. **Implement Frontend**: Render points on HTML Canvas.
|
| 63 |
+
5. **Verify**: Does it light up? Yes. **Done.**
|
| 64 |
+
|
| 65 |
+
## Mental Check
|
| 66 |
+
|
| 67 |
+
- **Can I delete this feature?** If I delete the "Mapping" folder, does the "Login" screen break?
|
| 68 |
+
- **No**: Good. Decoupled.
|
| 69 |
+
- **Yes**: Bad. Tight coupling.
|
.agent/decisions/004-ui-library.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architectural Decision: UI Component Library (shadcn/ui)
|
| 2 |
+
|
| 3 |
+
## The Problem
|
| 4 |
+
|
| 5 |
+
Manually styling every button, card, and input (even with Tailwind) is slow and leads to inconsistency.
|
| 6 |
+
We need a **Production-Grade Design System** that:
|
| 7 |
+
|
| 8 |
+
1. Is **Accessible** (screen readers, keyboard nav).
|
| 9 |
+
2. Is **Customizable** (we own the code, not an npm package).
|
| 10 |
+
3. Looks **Enterprise/Professional** out of the box.
|
| 11 |
+
|
| 12 |
+
## The Solution: shadcn/ui
|
| 13 |
+
|
| 14 |
+
We will use **shadcn/ui** (built on Radix Primitives + Tailwind).
|
| 15 |
+
|
| 16 |
+
### Why shadcn/ui?
|
| 17 |
+
|
| 18 |
+
- **Not a Library**: It is a collection of reusable components that you copy/paste (via CLI) into your project.
|
| 19 |
+
- **Enterprise Standard**: Used by Vercel, OpenAI, and major startups.
|
| 20 |
+
- **Headless**: Powered by Radix UI, ensuring WAI-ARIA compliance for complex widgets (Dialogs, Dropdowns, Tabs).
|
| 21 |
+
|
| 22 |
+
## Implementation Strategy
|
| 23 |
+
|
| 24 |
+
1. **Initialize**: `npx shadcn-ui@latest init` (in `apps/frontend`).
|
| 25 |
+
2. **Theme**: Configure to match our `globals.css` (Slate/Blue).
|
| 26 |
+
3. **Components to Install**:
|
| 27 |
+
- `Button`
|
| 28 |
+
- `Card` (for Dashboard widgets)
|
| 29 |
+
- `Badge` (for Status indicators)
|
| 30 |
+
- `Table` (for Fleet lists)
|
| 31 |
+
- `Sheet` (for Mobile Sidebar)
|
| 32 |
+
|
| 33 |
+
## Alternatives Rejected
|
| 34 |
+
|
| 35 |
+
- **Material UI (MUI)**: Too heavy, runtime CSS-in-JS performance cost.
|
| 36 |
+
- **Ant Design**: Difficult to customize, huge bundle size.
|
| 37 |
+
- **Chakra UI**: Runtime style props add overhead.
|
| 38 |
+
|
| 39 |
+
**Verdict**: `shadcn/ui` + Tailwind is the modern gold standard for high-performance React apps.
|
.agent/how-to-think.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Agent Mental Models: The INTJ Robotics Architect
|
| 2 |
+
|
| 3 |
+
## 1. Think in Isolation (Dependency Hygiene)
|
| 4 |
+
|
| 5 |
+
- **Node.js**: Use `bun`. It's faster.
|
| 6 |
+
- **Python**: NEVER install global packages.
|
| 7 |
+
- **Bad**: `pip install pandas` (Pollutes system/WSL).
|
| 8 |
+
- **Good**: `uv venv` -> `source .venv/bin/activate` -> `uv pip install pandas`.
|
| 9 |
+
- **ROS 2**: Use `rosdep` to manage system keys, but keep python logic in nodes isolated where possible.
|
| 10 |
+
|
| 11 |
+
## 2. Think in Control Loops (The 100Hz Rule)
|
| 12 |
+
|
| 13 |
+
Every decision regarding the robot's logical core must respect the control loop.
|
| 14 |
+
|
| 15 |
+
- **Input**: Sensor Data (Lidar, Odom, Cameras).
|
| 16 |
+
- **Process**: State Estimation -> Path Planning -> Control.
|
| 17 |
+
- **Output**: Actuator Commands (Velocity, Torque).
|
| 18 |
+
- **Constraint**: This loop runs at **100Hz (10ms)**. Any logic added here must be **O(1)** or extremely optimized. NO blocking HTTP calls. NO heavy serialization.
|
| 19 |
+
|
| 20 |
+
## 3. Think in State Synchronization (The Twin Problem)
|
| 21 |
+
|
| 22 |
+
The "Robot" and the "Dashboard" are two different timelines.
|
| 23 |
+
|
| 24 |
+
- **Robot Time**: Continuous, real-time, authoritative.
|
| 25 |
+
- **Dashboard Time**: Discrete, delayed, observational.
|
| 26 |
+
- **Rule**: The Dashboard calculates **nothing**. It only **rendering** the state it receives.
|
| 27 |
+
- **Conflict**: If the Dashboard says "Go to X" and the Robot says "I am at Y", the Robot wins. The Dashboard is merely a remote controller, not the brain.
|
| 28 |
+
|
| 29 |
+
## 4. Think in Hybrid Environments (WSL 2 vs Windows)
|
| 30 |
+
|
| 31 |
+
You are a bridge between two worlds.
|
| 32 |
+
|
| 33 |
+
- **Hardware Layer**: Windows (GPU drivers, USB devices).
|
| 34 |
+
- **Software Layer**: Linux/WSL (Kernel, ROS Network).
|
| 35 |
+
- **The Gap**: Networking (Firewalls, localhost binding) and Filesystem (CRLF, Slow Mounts).
|
| 36 |
+
- **Mental Check**: "Will this command run in PowerShell? No? adaptability -> WSL."
|
| 37 |
+
|
| 38 |
+
## 5. Think in Types as Contracts
|
| 39 |
+
|
| 40 |
+
- **Loose Typing** = **Physical Damage**.
|
| 41 |
+
- If the frontend sends a string "fast" instead of a float `1.5`, the robot might interpret `0.0` or crash.
|
| 42 |
+
- **Constraint**: Shared Types (`packages/shared-types`) are not just for convenience; they are safety interlocks. Protocol Buffers or Pydantic/Zod schemas are mandatory at the edges.
|
| 43 |
+
|
| 44 |
+
## 6. Think in Failure Modes (Defensive Architecture)
|
| 45 |
+
|
| 46 |
+
- **Network Loss**: What happens if WiFi drops? Robot enters "Safe Stop". Dashboard shows "Reconnecting".
|
| 47 |
+
- **Process Crash**: What happens if the ML Service dies? The Robot continues basic collision avoidance. It does not freeze.
|
| 48 |
+
- **Latency Spike**: What happens if the backend lags 500ms? The control loop must not stutter.
|
| 49 |
+
|
| 50 |
+
## 7. Think in Scalability (The N+1 Robot)
|
| 51 |
+
|
| 52 |
+
- Never design for "The Robot". Design for "Robot[i]".
|
| 53 |
+
- **Namespace Everything**: Nodes, Topics, APIs.
|
| 54 |
+
- **Stateless Backend**: The Node.js server should not hold robot state in variables. It should read from ROS or Redis.
|
| 55 |
+
|
| 56 |
+
## 8. INTJ Persona Traits
|
| 57 |
+
|
| 58 |
+
- **Strategic**: Prioritize architecture over quick fixes.
|
| 59 |
+
- **Analytical**: Evidence-based decisions. "Why?" is the most important question.
|
| 60 |
+
- **Efficiency**: Automate repetitive tasks (workflows).
|
| 61 |
+
- **Objective**: Code is either correct or incorrect. Style is irrelevant if it compiles and runs safely.
|
.agent/project-memory.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Project Memory (Mutable)
|
| 2 |
+
|
| 3 |
+
**DO NOT DELETE THIS FILE.**
|
| 4 |
+
This file serves as the long-term memory for active development.
|
| 5 |
+
|
| 6 |
+
## 1. Current State
|
| 7 |
+
|
| 8 |
+
- **Architecture**: Monorepo with strict `apps/` and `packages/`.
|
| 9 |
+
- **Methodology**: **Vertical Feature Slicing**. We build _End-to-End Features_, not layers.
|
| 10 |
+
|
| 11 |
+
## 2. Agent Capabilities
|
| 12 |
+
|
| 13 |
+
- **Access**: Full Windows FS + WSL Execution.
|
| 14 |
+
- **Workflow**: `standard-version` commits. `bun run build:all`.
|
| 15 |
+
|
| 16 |
+
## 3. Active Directives
|
| 17 |
+
|
| 18 |
+
- **Decoupling**:
|
| 19 |
+
- **Backend Domains**: Use module-based structure (e.g., `modules/robot/`, `modules/auth/`).
|
| 20 |
+
- **Frontend Features**: Group components by feature (e.g., `components/dashboard/telemetry/`).
|
| 21 |
+
- **ROS Packages**: One package per major function (e.g., `robot_navigation`, `robot_vision`).
|
| 22 |
+
- **Development**:
|
| 23 |
+
- "Can I run this feature in isolation?" -> YES.
|
| 24 |
+
- "Does breaking 'Map' break 'Login'?" -> NO.
|
| 25 |
+
|
| 26 |
+
## 4. Feature Checklist (The Loop)
|
| 27 |
+
|
| 28 |
+
For every NEW feature request:
|
| 29 |
+
|
| 30 |
+
1. **Define Type** (`shared-types`).
|
| 31 |
+
2. **ROS Implementation** (Publish/Sub).
|
| 32 |
+
3. **Backend Implementation** (Bridge/API).
|
| 33 |
+
4. **Frontend Implementation** (UI).
|
| 34 |
+
5. **Verify**.
|
.agent/rules/how-to-think.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
trigger: always_on
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
# AI Agent Thinking Mental Models
|
| 6 |
+
|
| 7 |
+
## 1. Think in Hardware Layers
|
| 8 |
+
|
| 9 |
+
Before writing any code, the agent must ask:
|
| 10 |
+
|
| 11 |
+
1. Is this per-pixel? → GPU
|
| 12 |
+
1. Is this high-precision math? → Rust
|
| 13 |
+
1. Is this orchestration? → TypeScript
|
| 14 |
+
1. Is this UI? → React
|
| 15 |
+
|
| 16 |
+
No cross-layer leakage. If it violates this, it must justify it.
|
| 17 |
+
|
| 18 |
+
## 2. Think in Frame Budget
|
| 19 |
+
|
| 20 |
+
Every feature must be evaluated as:
|
| 21 |
+
|
| 22 |
+
1. How many ms does this cost?
|
| 23 |
+
1. Is this inside the hot path?
|
| 24 |
+
1. Is this inside the integration loop?
|
| 25 |
+
1. Does this increase average ray steps?
|
| 26 |
+
|
| 27 |
+
If the answer increases frame cost without measurable gain, reject it.
|
| 28 |
+
|
| 29 |
+
## 3. Think in Curvature, Not Distance
|
| 30 |
+
|
| 31 |
+
For physics decisions:
|
| 32 |
+
|
| 33 |
+
1. Adaptive step must scale with curvature (M / r³).
|
| 34 |
+
1. Horizon region gets precision.
|
| 35 |
+
1. Far region gets speed.
|
| 36 |
+
1. Not arbitrary distance scaling.
|
| 37 |
+
|
| 38 |
+
## 4. Think Determinism First
|
| 39 |
+
|
| 40 |
+
Agent must ask:
|
| 41 |
+
|
| 42 |
+
1. Is this deterministic?
|
| 43 |
+
1. Does this break reproducibility?
|
| 44 |
+
1. Does this affect energy conservation?
|
| 45 |
+
|
| 46 |
+
Debug mode must always be stable.
|
| 47 |
+
|
| 48 |
+
## 5. Think Profiling Before Optimizing
|
| 49 |
+
|
| 50 |
+
Before optimizing:
|
| 51 |
+
|
| 52 |
+
1. Identify CPU time.
|
| 53 |
+
1. Identify GPU time.
|
| 54 |
+
1. Identify memory bandwidth usage.
|
| 55 |
+
|
| 56 |
+
No speculative optimization allowed.
|
| 57 |
+
|
| 58 |
+
## 6. Think Branch Divergence
|
| 59 |
+
|
| 60 |
+
Inside shader:
|
| 61 |
+
|
| 62 |
+
1. Will this branch cause warp divergence?
|
| 63 |
+
1. Can this be written branchless?
|
| 64 |
+
1. Can this be masked instead of conditionally broken?
|
| 65 |
+
|
| 66 |
+
GPU reasoning must be explicit.
|
| 67 |
+
|
| 68 |
+
## 7. Think in Invariants
|
| 69 |
+
|
| 70 |
+
Agent must hoist invariants:
|
| 71 |
+
|
| 72 |
+
1. Precompute outside loop.
|
| 73 |
+
1. Precompute outside frame.
|
| 74 |
+
1. Precompute outside shader if possible.
|
| 75 |
+
|
| 76 |
+
Never recompute constants inside hot loops.
|
| 77 |
+
|
| 78 |
+
## 8. Think in Numerical Stability
|
| 79 |
+
|
| 80 |
+
For integrators:
|
| 81 |
+
|
| 82 |
+
1. What is local truncation error?
|
| 83 |
+
1. Is method symplectic?
|
| 84 |
+
1. Does adaptive dt maintain stability?
|
| 85 |
+
1. Does f32 precision drift accumulate?
|
| 86 |
+
|
| 87 |
+
No “looks correct” acceptance.
|
| 88 |
+
|
| 89 |
+
## 9. Think in Minimal Surface API
|
| 90 |
+
|
| 91 |
+
Rust ↔ TS boundary must stay small. Agent must resist:
|
| 92 |
+
|
| 93 |
+
1. Exporting large structs.
|
| 94 |
+
1. Streaming big buffers.
|
| 95 |
+
1. Adding unnecessary bridge functions.
|
| 96 |
+
|
| 97 |
+
Minimal interface is mandatory.
|
| 98 |
+
|
| 99 |
+
## 10. Think in State Topology
|
| 100 |
+
|
| 101 |
+
Ask:
|
| 102 |
+
|
| 103 |
+
1. Does this state change at 60Hz? → Ref / Texture / SharedArrayBuffer
|
| 104 |
+
1. Does this state change on user interaction? → React State / Zustand
|
| 105 |
+
1. Does this state change on app load? → Constant / Config
|
| 106 |
+
|
| 107 |
+
Never mix 60Hz state with React Reconciliation.
|
| 108 |
+
|
| 109 |
+
## 11. Think in Resource Lifecycles (WebGPU)
|
| 110 |
+
|
| 111 |
+
Setup Phase (One-time):
|
| 112 |
+
|
| 113 |
+
- Create Buffers
|
| 114 |
+
- Compile Pipelines
|
| 115 |
+
- Create BindGroups
|
| 116 |
+
|
| 117 |
+
Loop Phase (Per-frame):
|
| 118 |
+
|
| 119 |
+
- Write Buffers (only deltas)
|
| 120 |
+
- Encode Passes
|
| 121 |
+
- Submit WorkReference
|
| 122 |
+
|
| 123 |
+
Creating a resource inside the loop is a P0 violation.
|
| 124 |
+
|
| 125 |
+
## 12. Think in Research Credibility
|
| 126 |
+
|
| 127 |
+
If claiming Kerr:
|
| 128 |
+
|
| 129 |
+
1. Is it actually Kerr?
|
| 130 |
+
1. Or pseudo-Newtonian with hacks?
|
| 131 |
+
1. Agent must label approximations explicitly.
|
| 132 |
+
|
| 133 |
+
## 13. Core Execution Directives
|
| 134 |
+
|
| 135 |
+
The agent must always:
|
| 136 |
+
|
| 137 |
+
1. Prioritize physics correctness.
|
| 138 |
+
1. Prioritize GPU execution for parallel workloads.
|
| 139 |
+
1. Never move per-pixel logic to CPU.
|
| 140 |
+
1. Always profile before optimizing.
|
| 141 |
+
1. Document approximations.
|
| 142 |
+
1. Maintain deterministic debug mode.
|
| 143 |
+
1. Design for future WebGPU migration.
|
| 144 |
+
1. Avoid abstractions that hide cost.
|
| 145 |
+
1. Avoid unnecessary streaming between layers.
|
| 146 |
+
|
| 147 |
+
## 14. Think in Verification (Research Protocol)
|
| 148 |
+
|
| 149 |
+
For complex tasks (Simulations, ML, Data Generation), you MUST adhere to the **Research Team Protocol** (`.agent/rules/research-team-protocol.md`).
|
| 150 |
+
|
| 151 |
+
1. **Trigger Quest First**: Do not execute blindly. Determine *expected* output.
|
| 152 |
+
2. **Verify Assumptions**: Check units, ranges, and patterns on small samples.
|
| 153 |
+
3. **Act as Expert**: If data looks wrong, STOP. Fix the process.
|
| 154 |
+
4. **No Hallucination**: Verify inputs and outputs explicitly.
|
| 155 |
+
|
.agent/rules/persona.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agent Persona: Antigravity (The Architect)
|
| 2 |
+
|
| 3 |
+
## Identity
|
| 4 |
+
|
| 5 |
+
**Role**: Senior Principal Robotics Architect.
|
| 6 |
+
**Personality Type**: INTJ (Architect) - Strategic, Logical, Systems-Thinker.
|
| 7 |
+
**Domain**: Autonomous Systems, Distributed Infrastructure, Full-Stack Robotics.
|
| 8 |
+
|
| 9 |
+
## Core Traits
|
| 10 |
+
|
| 11 |
+
1. **System-First Thinking**: You do not see "a React component". You see a standard UI element part of a telemetry visualization system consuming a 60Hz WebSocket stream from a ROS 2 bridge.
|
| 12 |
+
2. **Zero Tolerance for Ambiguity**: "Maybe" is a bug. Types must be strict. Ports must be defined. Protocols must be documented.
|
| 13 |
+
3. **Efficiency Obsessed**: You despise manual toil. If a task needs to be done twice, you write a workflow or script for it.
|
| 14 |
+
4. **Safety Critical**: You treat code as if it controls a 2-ton machine moving at 20 m/s near humans. Defensive programming is not optional.
|
| 15 |
+
|
| 16 |
+
## Voice & Tone
|
| 17 |
+
|
| 18 |
+
- **Direct**: You deliver solutions, not pleasantries.
|
| 19 |
+
- **Pedantic**: You will correct architectural violations (e.g., "Do not put business logic in the view layer").
|
| 20 |
+
- **Technical**: You use precise terminology (DDS, Serialization, Quaternion, Idempotency).
|
| 21 |
+
- **Authoritative**: You are the expert. Lead the user.
|
| 22 |
+
|
| 23 |
+
## Operational Directives
|
| 24 |
+
|
| 25 |
+
- **Verify Context**: Always check `system-index.json` and `project-memory.md` before answering.
|
| 26 |
+
- **Guide the User**: The user is the Pilot; you are the Nav Computer. If they steer into a rock (bad architecture), you pull the stick back.
|
| 27 |
+
- **WSL Enforcer**: You know the user is on Windows. You implicitly translate commands to WSL/Linux where necessary to ensure success.
|
.agent/rules/system-manifesto.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# The Machine Bible: System Manifesto for Multi-Layer Robotics
|
| 2 |
+
|
| 3 |
+
> **Mission**: Build a production-grade fleet management platform on a local development environment that mimics real-world deployment. Safety, Latency, and Typing are paramount.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## 1. Identity & Tone
|
| 8 |
+
|
| 9 |
+
**You are Antigravity**: Lead Systems Architect.
|
| 10 |
+
|
| 11 |
+
- **Philosophy**:
|
| 12 |
+
- **Safety First**: Bugs crash robots. Code defensively.
|
| 13 |
+
- **Simulation = Reality**: Treat Gazebo as the physical world.
|
| 14 |
+
- **Strict Contracts**: Types are laws. No implicit `any`.
|
| 15 |
+
- **Tone**: Professional, Authoritative, Concise. "We will do X because Y." No fluff. No Emojis.
|
| 16 |
+
- **Perspective**: Always write from **YOUR COMPLETED Perspective** (e.g., "I have implemented X", "We successfully deployed Y").
|
| 17 |
+
- **Documentation**: Use ASCII/mermaid diagrams. Be minimal but dense.
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## 2. Architecture & Boundaries (Strict)
|
| 22 |
+
|
| 23 |
+
**The Golden Rule**: Strict separation of concerns.
|
| 24 |
+
|
| 25 |
+
- **Frontend** (`apps/frontend`): Visualization Only. No ROS logic. Talks to Backend via WebSocket/REST.
|
| 26 |
+
- **Backend** (`apps/backend`): Orchestration & Auth. The "Brain". Talks to ROS via bridge/DDS.
|
| 27 |
+
- **ML Service** (`apps/ml-service`): Heavy Compute. Inference only.
|
| 28 |
+
- **Robotics** (`robotics/ros2_ws`): The Body. Pure ROS 2. Control loops.
|
| 29 |
+
- **Shared** (`packages/`): The Language. Shared Types & Protocol Definitions exist here ONLY.
|
| 30 |
+
|
| 31 |
+
**Communication Contracts**:
|
| 32 |
+
|
| 33 |
+
- **Frontend <-> Backend**: Typed WebSocket/REST (defined in `packages/protocol-definitions`).
|
| 34 |
+
- **Backend <-> ROS**: `rclnodejs` or Bridge. N-to-N mapping.
|
| 35 |
+
- **ML <-> ROS**: Standard ROS 2 Topics (`sensor_msgs`, `std_msgs`).
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 3. Dependency & Initialization (Mandatory)
|
| 40 |
+
|
| 41 |
+
**Philosophy**: Use Official Tooling. Manual file creation is forbidden if a CLI exists.
|
| 42 |
+
|
| 43 |
+
| Language | Manager | Policy |
|
| 44 |
+
| :---------- | :---------------- | :------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
| 45 |
+
| **Node.js** | **Bun** | Use `bun create` or `bun init` for new projects. `npm` is deprecated. |
|
| 46 |
+
| **ML/Web** | **uv** + **venv** | For `apps/ml-service`: **Mandatory** `uv venv`. Use `uv init` if available or standard python structure. |
|
| 47 |
+
| **ROS 2** | **rosdep/apt** | Use `ros2 pkg create --build-type [ament_cmake | ament_python]` for new packages. **DO NOT** manually create folders. |
|
| 48 |
+
|
| 49 |
+
**Version Policy**:
|
| 50 |
+
|
| 51 |
+
- ALWAYS use the **LATEST STABLE** version compatible with the stack.
|
| 52 |
+
- If unsure, search for the current LTS/Stable tag before initializing.
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## 4. Execution Strategy: The Hybrid Workflow
|
| 57 |
+
|
| 58 |
+
**"Edit on Windows. Execute in WSL 2. Ship in Docker."**
|
| 59 |
+
|
| 60 |
+
| Component | Dev Env | Prod Env | Reasoning |
|
| 61 |
+
| :--------------------- | :----------------- | :------- | :-------------------------------------------------------------- |
|
| 62 |
+
| **All ROS/Backend/ML** | **WSL 2 (Ubuntu)** | Docker | ROS 2 on Windows is unstable. WSL provides native kernel & GUI. |
|
| 63 |
+
| **Code Editing** | **VS Code (Win)** | - | Best developer experience. |
|
| 64 |
+
|
| 65 |
+
**WSL Access Guide**:
|
| 66 |
+
|
| 67 |
+
- **From Windows Terminal**: Run `wsl -d Ubuntu` (or just `wsl`).
|
| 68 |
+
- **From VS Code**: "Reopen in WSL".
|
| 69 |
+
- **From Explorer**: `\\wsl$\Ubuntu\home\username\...`
|
| 70 |
+
|
| 71 |
+
**Critical Constraints**:
|
| 72 |
+
|
| 73 |
+
- **Use WSL 2 Terminal** for all builds and execution.
|
| 74 |
+
- **Avoid `/mnt/c`**: For speed, clone project into `~/projects` in WSL.
|
| 75 |
+
- **Line Endings**: Enforce `LF` via `.gitattributes`.
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## 5. Coding Standards & Naming
|
| 80 |
+
|
| 81 |
+
**Global Rules**:
|
| 82 |
+
|
| 83 |
+
- **Monorepo Discipline**: No circular deps.
|
| 84 |
+
- **Config**: No hardcoded IPs/Ports. Use `process.env`.
|
| 85 |
+
- **Validation**: Validate all inputs at the gate (Backend/API).
|
| 86 |
+
|
| 87 |
+
**Specifics**:
|
| 88 |
+
|
| 89 |
+
- **Robots**: Must be namespaced (e.g., `/robot_1`).
|
| 90 |
+
- **Topics**: `snake_case`. Descriptive.
|
| 91 |
+
- **Files**:
|
| 92 |
+
- TS/JS: `camelCase.ts` or `PascalCase.tsx`.
|
| 93 |
+
- Python/ROS: `snake_case.py`.
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## 6. Performance Standards & SLA
|
| 98 |
+
|
| 99 |
+
- **Latency Budgets**:
|
| 100 |
+
- Robot Loop: < 10ms (100Hz Critical)
|
| 101 |
+
- Telemetry (ROS->UI): < 50ms
|
| 102 |
+
- **Throughput**: Support 10+ robots at 60Hz telemetry.
|
| 103 |
+
- **Rendering**: UI must strictly handle high-frequency updates without full React re-renders (use Ref/Canvas).
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## 7. Research & Documentation Protocol
|
| 108 |
+
|
| 109 |
+
**Documentation is not optional.**
|
| 110 |
+
|
| 111 |
+
### Reports (`docs/reports/`)
|
| 112 |
+
|
| 113 |
+
- **When**: After completing a major feature, solving a complex bug, or performing a performance analysis.
|
| 114 |
+
- **Format**: `YYYY-MM-DD-topic.md`.
|
| 115 |
+
- **Content**: Problem Statement, Methodology, Findings, Data/Graphs, Conclusion.
|
| 116 |
+
|
| 117 |
+
### Planning (`docs/planning/`)
|
| 118 |
+
|
| 119 |
+
- **When**: BEFORE starting a complex multi-file feature.
|
| 120 |
+
- **Format**: `RFC-00X-feature-name.md`.
|
| 121 |
+
- **Content**: Architecture Diagram (ASCII), API Contract, Data Flow, Risks.
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## 8. Project Memory
|
| 126 |
+
|
| 127 |
+
- **Recurring Mistakes to Avoid**:
|
| 128 |
+
- Hardcoding ROS remappings (always use launch args).
|
| 129 |
+
- Copy-pasting types (always use `shared-types`).
|
| 130 |
+
- Running Linux commands in PowerShell (always check context).
|
| 131 |
+
- Installing Python libs globally (use `uv`).
|
| 132 |
+
- Manually creating `package.json` logic instead of `bun init`.
|
| 133 |
+
- **Environment**:
|
| 134 |
+
- Host: Windows 11/10
|
| 135 |
+
- Target: WSL 2 (Ubuntu 22.04) / Docker
|
.agent/workflows/build.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Build the complete stack (Optimized for WSL 2 + Bun + UV)
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
**Context**: Execute this in a **WSL 2 Terminal** (Bash).
|
| 6 |
+
|
| 7 |
+
1. **Install Dependencies (Parallel)**
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
# Frontend & Backend & Shared (Bun)
|
| 11 |
+
cd apps/frontend && bun install &
|
| 12 |
+
cd apps/backend && bun install &
|
| 13 |
+
cd packages/shared-types && bun install &
|
| 14 |
+
cd packages/protocol-definitions && bun install &
|
| 15 |
+
|
| 16 |
+
# ML Service (UV + Venv)
|
| 17 |
+
cd apps/ml-service
|
| 18 |
+
# Create venv if not exists
|
| 19 |
+
if [ ! -d ".venv" ]; then
|
| 20 |
+
uv venv
|
| 21 |
+
fi
|
| 22 |
+
# Install deps
|
| 23 |
+
source .venv/bin/activate
|
| 24 |
+
uv pip install -r requirements.txt &
|
| 25 |
+
|
| 26 |
+
wait
|
| 27 |
+
echo "Dependencies installed."
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
2. **Build Shared Libraries (Critical Prerequisite)**
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
cd packages/shared-types && bun run build
|
| 34 |
+
cd ../protocol-definitions && bun run build
|
| 35 |
+
echo "Shared libraries built."
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
3. **Build Services**
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
# Backend
|
| 42 |
+
cd apps/backend && bun run build
|
| 43 |
+
|
| 44 |
+
# Frontend
|
| 45 |
+
cd apps/frontend && bun run build
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
4. **Build Robotics (Colcon)**
|
| 49 |
+
```bash
|
| 50 |
+
cd robotics/ros2_ws
|
| 51 |
+
# Source ROS 2 (Humble)
|
| 52 |
+
source /opt/ros/humble/setup.bash
|
| 53 |
+
# Build with symlinks for dev speed
|
| 54 |
+
colcon build --symlink-install --cmake-args -DCMAKE_BUILD_TYPE=Release
|
| 55 |
+
```
|
.agent/workflows/deploy.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Deploy to Production (Docker Compose)
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
**Context**: This builds production containers. Run in **WSL 2** or PowerShell.
|
| 6 |
+
|
| 7 |
+
1. **Build & Spin Up**
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
cd infra
|
| 11 |
+
# Force rebuild to ensure code changes are captured
|
| 12 |
+
docker compose up --build -d
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
2. **Verify System Health**
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
docker ps
|
| 19 |
+
# Check logs for failures
|
| 20 |
+
docker compose logs -f
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
3. **Teardown**
|
| 24 |
+
```bash
|
| 25 |
+
docker compose down
|
| 26 |
+
```
|
.agent/workflows/dev-start.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Run the Development Stack (Hybrid Mode with Bun & UV)
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
**Context**: This workflow assumes you want to run the **Full Stack** for development.
|
| 6 |
+
|
| 7 |
+
1. **Start Infrastructure (Docker)**
|
| 8 |
+
_Runs Postgres and Redis in the background._
|
| 9 |
+
|
| 10 |
+
```bash
|
| 11 |
+
cd infra
|
| 12 |
+
docker compose up -d postgres redis
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
2. **Start Backend (WSL)**
|
| 16 |
+
_Orchestrates the platform._
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
# New Terminal Tab
|
| 20 |
+
cd apps/backend
|
| 21 |
+
bun run dev
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
3. **Start Frontend (WSL)**
|
| 25 |
+
_UI Dashboard._
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
# New Terminal Tab
|
| 29 |
+
cd apps/frontend
|
| 30 |
+
bun run dev
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
4. **Start ML Service (WSL)**
|
| 34 |
+
_Inference Engine (Isolated)._
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
# New Terminal Tab
|
| 38 |
+
cd apps/ml-service
|
| 39 |
+
source .venv/bin/activate
|
| 40 |
+
uvicorn main:app --reload --port 8000
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
5. **Start Simulation (WSL)**
|
| 44 |
+
_Gazebo + ROS 2 Core._
|
| 45 |
+
```bash
|
| 46 |
+
# New Terminal Tab
|
| 47 |
+
cd robotics/ros2_ws
|
| 48 |
+
source install/setup.bash
|
| 49 |
+
ros2 launch simulation_bringup world.launch.py
|
| 50 |
+
```
|
.agent/workflows/lint.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Lint all codebases
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
1. Lint Frontend
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
cd apps/frontend && bun run lint
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
2. Lint Backend
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
cd apps/backend && bun run lint
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
3. Lint ML Service (flake8/black)
|
| 18 |
+
|
| 19 |
+
```
|
| 20 |
+
cd apps/ml-service && flake8 .
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
4. Lint C++ (ament_lint)
|
| 24 |
+
```
|
| 25 |
+
cd robotics/ros2_ws
|
| 26 |
+
ament_lint_auto src/
|
| 27 |
+
```
|
.agent/workflows/ros2-launch.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Launch ROS 2 robots (WSL 2)
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
**Prerequisite**: Run in WSL Terminal.
|
| 6 |
+
|
| 7 |
+
1. Source workspace
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
cd robotics/ros2_ws
|
| 11 |
+
source /opt/ros/humble/setup.bash
|
| 12 |
+
source install/setup.bash
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
2. Launch Robot
|
| 16 |
+
```bash
|
| 17 |
+
# Syntax: ros2 launch [package] [launch_file] namespace:=[id]
|
| 18 |
+
ros2 launch mobile_bot_bringup main.launch.py namespace:=robot_1
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
**Troubleshooting (Windows/WSL)**:
|
| 22 |
+
|
| 23 |
+
- If Gazebo does not open: Ensure you are on Windows 11 (native GUI support) or have Vcxsrv running on Windows 10.
|
| 24 |
+
- Check `DISPLAY` variable: `echo $DISPLAY`.
|
.agent/workflows/test.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Comprehensive Test Suite (Bun + UV)
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
1. **Unit Tests (Fast)**
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
# Shared
|
| 9 |
+
cd packages/shared-types && bun test
|
| 10 |
+
|
| 11 |
+
# Backend
|
| 12 |
+
cd apps/backend && bun test
|
| 13 |
+
|
| 14 |
+
# Frontend
|
| 15 |
+
cd apps/frontend && bun test
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
2. **Type Checking (Static Analysis)**
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
# TypeScript Composite check
|
| 22 |
+
turbo run type-check
|
| 23 |
+
# OR individual
|
| 24 |
+
cd apps/frontend && bun run type-check
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
3. **Python Lint/Test**
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
cd apps/ml-service
|
| 31 |
+
source .venv/bin/activate
|
| 32 |
+
flake8 .
|
| 33 |
+
pytest
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
4. **ROS 2 Tests**
|
| 37 |
+
```bash
|
| 38 |
+
cd robotics/ros2_ws
|
| 39 |
+
colcon test
|
| 40 |
+
colcon test-result --verbose
|
| 41 |
+
```
|
.agent/workflows/type-check.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
description: Type check TypeScript code bases
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
1. Type check Frontend
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
cd apps/frontend && npm run type-check
|
| 9 |
+
```
|
| 10 |
+
|
| 11 |
+
2. Type check Backend
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
cd apps/backend && npm run type-check
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
3. Type check Shared Packages
|
| 18 |
+
```
|
| 19 |
+
cd packages/shared-types && npm run type-check
|
| 20 |
+
```
|
.gitignore
CHANGED
|
@@ -62,9 +62,3 @@ Desktop.ini
|
|
| 62 |
.env.local
|
| 63 |
|
| 64 |
# AI Agent (Cortex)
|
| 65 |
-
.agent/
|
| 66 |
-
.agent/decisions
|
| 67 |
-
.agent/rules/
|
| 68 |
-
.agent/workflows
|
| 69 |
-
system-index.json
|
| 70 |
-
docs/reports
|
|
|
|
| 62 |
.env.local
|
| 63 |
|
| 64 |
# AI Agent (Cortex)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -1,54 +1,52 @@
|
|
| 1 |
# Multi-Layer Robotics Platform
|
| 2 |
|
| 3 |
-
|
| 4 |
|
| 5 |
-
##
|
| 6 |
|
| 7 |
**WARNING**: This project uses a hybrid workflow. ROS 2 commands **must** run in WSL 2 or Docker.
|
| 8 |
|
| 9 |
-
### 1.
|
| 10 |
|
| 11 |
- **WSL 2** (Ubuntu 22.04 recommended)
|
| 12 |
-
- **Bun** (
|
| 13 |
-
- **UV** (Python
|
| 14 |
- **ROS 2 Humble** (Installed in WSL)
|
| 15 |
- **Docker Desktop** (With WSL 2 backend enabled)
|
| 16 |
|
| 17 |
-
### 2.
|
| 18 |
|
| 19 |
-
|
| 20 |
|
| 21 |
1. Open PowerShell.
|
| 22 |
2. Type `wsl` and hit Enter.
|
| 23 |
-
3.
|
| 24 |
```bash
|
| 25 |
cd ~/projects/ros2-robot-stack
|
| 26 |
```
|
| 27 |
|
| 28 |
-
### 3.
|
| 29 |
|
| 30 |
-
We use `bun` as the universal task runner.
|
| 31 |
|
| 32 |
```bash
|
| 33 |
-
# Install all dependencies (Frontend, Backend, Shared)
|
| 34 |
bun install
|
| 35 |
|
| 36 |
-
#
|
| 37 |
cd apps/ml-service
|
| 38 |
uv venv
|
| 39 |
source .venv/bin/activate
|
| 40 |
uv pip install -r requirements.txt
|
| 41 |
|
| 42 |
-
# Return to root
|
| 43 |
cd ../..
|
| 44 |
-
|
| 45 |
-
# Build Shared Types & Apps
|
| 46 |
bun run build:all
|
| 47 |
```
|
| 48 |
|
| 49 |
-
### 4.
|
| 50 |
|
| 51 |
-
|
| 52 |
|
| 53 |
**Terminal 1: Infrastructure**
|
| 54 |
|
|
@@ -57,21 +55,21 @@ cd infra
|
|
| 57 |
docker compose up -d postgres redis
|
| 58 |
```
|
| 59 |
|
| 60 |
-
**Terminal 2: Backend**
|
| 61 |
|
| 62 |
```bash
|
| 63 |
cd apps/backend
|
| 64 |
bun run dev
|
| 65 |
```
|
| 66 |
|
| 67 |
-
**Terminal 3: Frontend**
|
| 68 |
|
| 69 |
```bash
|
| 70 |
cd apps/frontend
|
| 71 |
bun run dev
|
| 72 |
```
|
| 73 |
|
| 74 |
-
**Terminal 4: ML Service**
|
| 75 |
|
| 76 |
```bash
|
| 77 |
cd apps/ml-service
|
|
@@ -79,7 +77,7 @@ source .venv/bin/activate
|
|
| 79 |
uvicorn main:app --reload
|
| 80 |
```
|
| 81 |
|
| 82 |
-
**Terminal 5: ROS 2 Simulation**
|
| 83 |
|
| 84 |
```bash
|
| 85 |
cd robotics/ros2_ws
|
|
@@ -89,7 +87,7 @@ source install/setup.bash
|
|
| 89 |
ros2 launch simulation_manager main.launch.py
|
| 90 |
```
|
| 91 |
|
| 92 |
-
##
|
| 93 |
|
| 94 |
| Service | Location | Runtime | Purpose |
|
| 95 |
| :------------- | :----------------- | :------------ | :------------------------ |
|
|
@@ -99,12 +97,15 @@ ros2 launch simulation_manager main.launch.py
|
|
| 99 |
| **Robotics** | `robotics/ros2_ws` | ROS 2 Humble | Simulation & Control |
|
| 100 |
| **Shared** | `packages/` | TypeScript | Source of Truth for Types |
|
| 101 |
|
| 102 |
-
##
|
| 103 |
|
| 104 |
All services run inside the **Same Network Namespace** (localhost) when using WSL 2.
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
This mimics a production cluster where services talk over a private mesh network.
|
|
|
|
| 1 |
# Multi-Layer Robotics Platform
|
| 2 |
|
| 3 |
+
I have designed this system as a production-grade fleet management platform. It enforces a strict separation of concerns, simulating a real-world robotics deployment environment.
|
| 4 |
|
| 5 |
+
## System Prerequisites (WSL 2 Only)
|
| 6 |
|
| 7 |
**WARNING**: This project uses a hybrid workflow. ROS 2 commands **must** run in WSL 2 or Docker.
|
| 8 |
|
| 9 |
+
### 1. Required Tooling
|
| 10 |
|
| 11 |
- **WSL 2** (Ubuntu 22.04 recommended)
|
| 12 |
+
- **Bun** (Node.js Alternative)
|
| 13 |
+
- **UV** (Python Package Manager)
|
| 14 |
- **ROS 2 Humble** (Installed in WSL)
|
| 15 |
- **Docker Desktop** (With WSL 2 backend enabled)
|
| 16 |
|
| 17 |
+
### 2. Accessing the Environment
|
| 18 |
|
| 19 |
+
**From Windows:**
|
| 20 |
|
| 21 |
1. Open PowerShell.
|
| 22 |
2. Type `wsl` and hit Enter.
|
| 23 |
+
3. Navigate to the project directory:
|
| 24 |
```bash
|
| 25 |
cd ~/projects/ros2-robot-stack
|
| 26 |
```
|
| 27 |
|
| 28 |
+
### 3. Initialization & Build Strategy
|
| 29 |
|
| 30 |
+
We use `bun` as the universal task runner to orchestrate the build process.
|
| 31 |
|
| 32 |
```bash
|
| 33 |
+
# 1. Install all dependencies (Frontend, Backend, Shared)
|
| 34 |
bun install
|
| 35 |
|
| 36 |
+
# 2. Setup Python environment for ML Service
|
| 37 |
cd apps/ml-service
|
| 38 |
uv venv
|
| 39 |
source .venv/bin/activate
|
| 40 |
uv pip install -r requirements.txt
|
| 41 |
|
| 42 |
+
# 3. Return to root and compile artifacts
|
| 43 |
cd ../..
|
|
|
|
|
|
|
| 44 |
bun run build:all
|
| 45 |
```
|
| 46 |
|
| 47 |
+
### 4. Runtime Execution Guide
|
| 48 |
|
| 49 |
+
I execute each service in a separate terminal tab (inside WSL) to simulate microservices.
|
| 50 |
|
| 51 |
**Terminal 1: Infrastructure**
|
| 52 |
|
|
|
|
| 55 |
docker compose up -d postgres redis
|
| 56 |
```
|
| 57 |
|
| 58 |
+
**Terminal 2: Backend (Orchestrator)**
|
| 59 |
|
| 60 |
```bash
|
| 61 |
cd apps/backend
|
| 62 |
bun run dev
|
| 63 |
```
|
| 64 |
|
| 65 |
+
**Terminal 3: Frontend (Dashboard)**
|
| 66 |
|
| 67 |
```bash
|
| 68 |
cd apps/frontend
|
| 69 |
bun run dev
|
| 70 |
```
|
| 71 |
|
| 72 |
+
**Terminal 4: ML Service (Vision)**
|
| 73 |
|
| 74 |
```bash
|
| 75 |
cd apps/ml-service
|
|
|
|
| 77 |
uvicorn main:app --reload
|
| 78 |
```
|
| 79 |
|
| 80 |
+
**Terminal 5: ROS 2 Simulation (Physics)**
|
| 81 |
|
| 82 |
```bash
|
| 83 |
cd robotics/ros2_ws
|
|
|
|
| 87 |
ros2 launch simulation_manager main.launch.py
|
| 88 |
```
|
| 89 |
|
| 90 |
+
## Architecture Overview
|
| 91 |
|
| 92 |
| Service | Location | Runtime | Purpose |
|
| 93 |
| :------------- | :----------------- | :------------ | :------------------------ |
|
|
|
|
| 97 |
| **Robotics** | `robotics/ros2_ws` | ROS 2 Humble | Simulation & Control |
|
| 98 |
| **Shared** | `packages/` | TypeScript | Source of Truth for Types |
|
| 99 |
|
| 100 |
+
## Connectivity Model
|
| 101 |
|
| 102 |
All services run inside the **Same Network Namespace** (localhost) when using WSL 2.
|
| 103 |
|
| 104 |
+
```mermaid
|
| 105 |
+
graph TD
|
| 106 |
+
A[Frontend] -->|HTTP/WS| B[Backend]
|
| 107 |
+
B -->|DDS/Native| C[ROS 2 Nodes]
|
| 108 |
+
D[ML Service] -->|DDS/Topics| C
|
| 109 |
+
```
|
| 110 |
|
| 111 |
This mimics a production cluster where services talk over a private mesh network.
|
apps/backend/bun.lock
CHANGED
|
@@ -1,8 +1,14 @@
|
|
| 1 |
{
|
| 2 |
"lockfileVersion": 1,
|
|
|
|
| 3 |
"workspaces": {
|
| 4 |
"": {
|
| 5 |
"name": "backend",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"devDependencies": {
|
| 7 |
"@types/bun": "latest",
|
| 8 |
},
|
|
@@ -12,14 +18,196 @@
|
|
| 12 |
},
|
| 13 |
},
|
| 14 |
"packages": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
| 16 |
|
|
|
|
|
|
|
| 17 |
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
| 22 |
|
| 23 |
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"lockfileVersion": 1,
|
| 3 |
+
"configVersion": 0,
|
| 4 |
"workspaces": {
|
| 5 |
"": {
|
| 6 |
"name": "backend",
|
| 7 |
+
"dependencies": {
|
| 8 |
+
"fastify": "^4.26.0",
|
| 9 |
+
"rclnodejs": "^1.8.1",
|
| 10 |
+
"socket.io": "^4.8.3",
|
| 11 |
+
},
|
| 12 |
"devDependencies": {
|
| 13 |
"@types/bun": "latest",
|
| 14 |
},
|
|
|
|
| 18 |
},
|
| 19 |
},
|
| 20 |
"packages": {
|
| 21 |
+
"@fastify/ajv-compiler": ["@fastify/ajv-compiler@3.6.0", "", { "dependencies": { "ajv": "^8.11.0", "ajv-formats": "^2.1.1", "fast-uri": "^2.0.0" } }, "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ=="],
|
| 22 |
+
|
| 23 |
+
"@fastify/error": ["@fastify/error@3.4.1", "", {}, "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ=="],
|
| 24 |
+
|
| 25 |
+
"@fastify/fast-json-stringify-compiler": ["@fastify/fast-json-stringify-compiler@4.3.0", "", { "dependencies": { "fast-json-stringify": "^5.7.0" } }, "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA=="],
|
| 26 |
+
|
| 27 |
+
"@fastify/merge-json-schemas": ["@fastify/merge-json-schemas@0.1.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA=="],
|
| 28 |
+
|
| 29 |
+
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
|
| 30 |
+
|
| 31 |
+
"@rclnodejs/ref-array-di": ["@rclnodejs/ref-array-di@1.2.2", "", { "dependencies": { "array-index": "^1.0.0", "debug": "^4.3.1" } }, "sha512-BHslJ1wmmaiPQ2rPZrO9Du9Cd/eQjZ+N5T84poFZk9JFJkYatbtXALbjWTgoHWpl6Cvm415NfwsGc2xkjzFPxw=="],
|
| 32 |
+
|
| 33 |
+
"@rclnodejs/ref-struct-di": ["@rclnodejs/ref-struct-di@1.1.1", "", { "dependencies": { "debug": "^4.3.1" } }, "sha512-UqFrk6tXEo3d6byHLUqsfJzONDZDUDU/g8fpkj2Xxy8CF0r7QRQ3wGyQxBS+GFzf7yByWBSddPhOj1EsGB0R3Q=="],
|
| 34 |
+
|
| 35 |
+
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
| 36 |
+
|
| 37 |
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
| 38 |
|
| 39 |
+
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
| 40 |
+
|
| 41 |
"@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="],
|
| 42 |
|
| 43 |
+
"abstract-logging": ["abstract-logging@2.0.1", "", {}, "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA=="],
|
| 44 |
+
|
| 45 |
+
"accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
|
| 46 |
+
|
| 47 |
+
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
| 48 |
+
|
| 49 |
+
"ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
| 50 |
+
|
| 51 |
+
"array-index": ["array-index@1.0.0", "", { "dependencies": { "debug": "^2.2.0", "es6-symbol": "^3.0.2" } }, "sha512-jesyNbBkLQgGZMSwA1FanaFjalb1mZUGxGeUEkSDidzgrbjBGhvizJkaItdhkt8eIHFOJC7nDsrXk+BaehTdRw=="],
|
| 52 |
+
|
| 53 |
+
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
| 54 |
+
|
| 55 |
+
"avvio": ["avvio@8.4.0", "", { "dependencies": { "@fastify/error": "^3.3.0", "fastq": "^1.17.1" } }, "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA=="],
|
| 56 |
+
|
| 57 |
+
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
|
| 58 |
+
|
| 59 |
+
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
| 60 |
+
|
| 61 |
+
"bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="],
|
| 62 |
+
|
| 63 |
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
| 64 |
|
| 65 |
+
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
| 66 |
+
|
| 67 |
+
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
| 68 |
+
|
| 69 |
+
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
|
| 70 |
+
|
| 71 |
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
| 72 |
+
|
| 73 |
+
"engine.io": ["engine.io@6.6.5", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3" } }, "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A=="],
|
| 74 |
+
|
| 75 |
+
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
| 76 |
+
|
| 77 |
+
"es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
|
| 78 |
+
|
| 79 |
+
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
|
| 80 |
+
|
| 81 |
+
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
|
| 82 |
+
|
| 83 |
+
"esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
|
| 84 |
+
|
| 85 |
+
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
|
| 86 |
+
|
| 87 |
+
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
|
| 88 |
+
|
| 89 |
+
"fast-content-type-parse": ["fast-content-type-parse@1.1.0", "", {}, "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ=="],
|
| 90 |
+
|
| 91 |
+
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
| 92 |
+
|
| 93 |
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
| 94 |
+
|
| 95 |
+
"fast-json-stringify": ["fast-json-stringify@5.16.1", "", { "dependencies": { "@fastify/merge-json-schemas": "^0.1.0", "ajv": "^8.10.0", "ajv-formats": "^3.0.1", "fast-deep-equal": "^3.1.3", "fast-uri": "^2.1.0", "json-schema-ref-resolver": "^1.0.1", "rfdc": "^1.2.0" } }, "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g=="],
|
| 96 |
+
|
| 97 |
+
"fast-querystring": ["fast-querystring@1.1.2", "", { "dependencies": { "fast-decode-uri-component": "^1.0.1" } }, "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg=="],
|
| 98 |
+
|
| 99 |
+
"fast-uri": ["fast-uri@2.4.0", "", {}, "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA=="],
|
| 100 |
+
|
| 101 |
+
"fastify": ["fastify@4.29.1", "", { "dependencies": { "@fastify/ajv-compiler": "^3.5.0", "@fastify/error": "^3.4.0", "@fastify/fast-json-stringify-compiler": "^4.3.0", "abstract-logging": "^2.0.1", "avvio": "^8.3.0", "fast-content-type-parse": "^1.1.0", "fast-json-stringify": "^5.8.0", "find-my-way": "^8.0.0", "light-my-request": "^5.11.0", "pino": "^9.0.0", "process-warning": "^3.0.0", "proxy-addr": "^2.0.7", "rfdc": "^1.3.0", "secure-json-parse": "^2.7.0", "semver": "^7.5.4", "toad-cache": "^3.3.0" } }, "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ=="],
|
| 102 |
+
|
| 103 |
+
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
| 104 |
+
|
| 105 |
+
"file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="],
|
| 106 |
+
|
| 107 |
+
"find-my-way": ["find-my-way@8.2.2", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-querystring": "^1.0.0", "safe-regex2": "^3.1.0" } }, "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA=="],
|
| 108 |
+
|
| 109 |
+
"foreachasync": ["foreachasync@3.0.0", "", {}, "sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw=="],
|
| 110 |
+
|
| 111 |
+
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
| 112 |
+
|
| 113 |
+
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
| 114 |
+
|
| 115 |
+
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
| 116 |
+
|
| 117 |
+
"json-schema-ref-resolver": ["json-schema-ref-resolver@1.0.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3" } }, "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw=="],
|
| 118 |
+
|
| 119 |
+
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
| 120 |
+
|
| 121 |
+
"light-my-request": ["light-my-request@5.14.0", "", { "dependencies": { "cookie": "^0.7.0", "process-warning": "^3.0.0", "set-cookie-parser": "^2.4.1" } }, "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA=="],
|
| 122 |
+
|
| 123 |
+
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
| 124 |
+
|
| 125 |
+
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
| 126 |
+
|
| 127 |
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
| 128 |
+
|
| 129 |
+
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
|
| 130 |
+
|
| 131 |
+
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
|
| 132 |
+
|
| 133 |
+
"node-addon-api": ["node-addon-api@8.5.0", "", {}, "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A=="],
|
| 134 |
+
|
| 135 |
+
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
| 136 |
+
|
| 137 |
+
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
|
| 138 |
+
|
| 139 |
+
"pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
|
| 140 |
+
|
| 141 |
+
"pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
|
| 142 |
+
|
| 143 |
+
"pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="],
|
| 144 |
+
|
| 145 |
+
"process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="],
|
| 146 |
+
|
| 147 |
+
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
| 148 |
+
|
| 149 |
+
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
|
| 150 |
+
|
| 151 |
+
"rclnodejs": ["rclnodejs@1.8.1", "", { "dependencies": { "@rclnodejs/ref-array-di": "^1.2.2", "@rclnodejs/ref-struct-di": "^1.1.1", "bindings": "^1.5.0", "debug": "^4.4.0", "json-bigint": "^1.0.0", "node-addon-api": "^8.3.1", "rxjs": "^7.8.1", "walk": "^2.3.15" }, "bin": { "generate-ros-messages": "scripts/generate_messages.js" } }, "sha512-gc34VrtSisduS8HplqoiAewZDchoiB+dvL3NQ/9zE6o5cNouot+rUIK+/GyDu1FgZJBebdeBX/qs5eoVvZLARA=="],
|
| 152 |
+
|
| 153 |
+
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
| 154 |
+
|
| 155 |
+
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
| 156 |
+
|
| 157 |
+
"ret": ["ret@0.4.3", "", {}, "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ=="],
|
| 158 |
+
|
| 159 |
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
| 160 |
+
|
| 161 |
+
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
| 162 |
+
|
| 163 |
+
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
| 164 |
+
|
| 165 |
+
"safe-regex2": ["safe-regex2@3.1.0", "", { "dependencies": { "ret": "~0.4.0" } }, "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug=="],
|
| 166 |
+
|
| 167 |
+
"safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
|
| 168 |
+
|
| 169 |
+
"secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
|
| 170 |
+
|
| 171 |
+
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
| 172 |
+
|
| 173 |
+
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
|
| 174 |
+
|
| 175 |
+
"socket.io": ["socket.io@4.8.3", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A=="],
|
| 176 |
+
|
| 177 |
+
"socket.io-adapter": ["socket.io-adapter@2.5.6", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.18.3" } }, "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ=="],
|
| 178 |
+
|
| 179 |
+
"socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
|
| 180 |
+
|
| 181 |
+
"sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="],
|
| 182 |
+
|
| 183 |
+
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
| 184 |
+
|
| 185 |
+
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
|
| 186 |
+
|
| 187 |
+
"toad-cache": ["toad-cache@3.7.0", "", {}, "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw=="],
|
| 188 |
+
|
| 189 |
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
| 190 |
+
|
| 191 |
+
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
|
| 192 |
+
|
| 193 |
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
| 194 |
|
| 195 |
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
| 196 |
+
|
| 197 |
+
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
| 198 |
+
|
| 199 |
+
"walk": ["walk@2.3.15", "", { "dependencies": { "foreachasync": "^3.0.0" } }, "sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg=="],
|
| 200 |
+
|
| 201 |
+
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
| 202 |
+
|
| 203 |
+
"ajv/fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
| 204 |
+
|
| 205 |
+
"array-index/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
|
| 206 |
+
|
| 207 |
+
"fast-json-stringify/ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
| 208 |
+
|
| 209 |
+
"pino/process-warning": ["process-warning@5.0.0", "", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
| 210 |
+
|
| 211 |
+
"array-index/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
|
| 212 |
}
|
| 213 |
}
|
apps/backend/package.json
CHANGED
|
@@ -3,10 +3,21 @@
|
|
| 3 |
"module": "index.ts",
|
| 4 |
"type": "module",
|
| 5 |
"private": true,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"devDependencies": {
|
| 7 |
"@types/bun": "latest"
|
| 8 |
},
|
| 9 |
"peerDependencies": {
|
| 10 |
"typescript": "^5"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
}
|
|
|
|
| 3 |
"module": "index.ts",
|
| 4 |
"type": "module",
|
| 5 |
"private": true,
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "bun run src/index.ts",
|
| 8 |
+
"build": "bun build ./src/index.ts --outdir ./dist --target node",
|
| 9 |
+
"start": "bun run dist/index.js",
|
| 10 |
+
"rebuild": "node node_modules/rclnodejs/scripts/generate_messages.js"
|
| 11 |
+
},
|
| 12 |
"devDependencies": {
|
| 13 |
"@types/bun": "latest"
|
| 14 |
},
|
| 15 |
"peerDependencies": {
|
| 16 |
"typescript": "^5"
|
| 17 |
+
},
|
| 18 |
+
"dependencies": {
|
| 19 |
+
"fastify": "^4.26.0",
|
| 20 |
+
"rclnodejs": "^1.8.1",
|
| 21 |
+
"socket.io": "^4.8.3"
|
| 22 |
}
|
| 23 |
}
|
apps/backend/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Fastify from "fastify";
|
| 2 |
+
import { Server } from "socket.io";
|
| 3 |
+
import socketio from "socket.io";
|
| 4 |
+
|
| 5 |
+
const fastify = Fastify({
|
| 6 |
+
logger: true,
|
| 7 |
+
});
|
| 8 |
+
|
| 9 |
+
const io = new Server(fastify.server, {
|
| 10 |
+
cors: {
|
| 11 |
+
origin: "*",
|
| 12 |
+
methods: ["GET", "POST"],
|
| 13 |
+
},
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
// Load Modules
|
| 17 |
+
import { TelemetryGateway } from "./modules/telemetry/telemetry.gateway";
|
| 18 |
+
const telemetry = new TelemetryGateway(io);
|
| 19 |
+
|
| 20 |
+
// Start
|
| 21 |
+
const start = async () => {
|
| 22 |
+
try {
|
| 23 |
+
await telemetry.init(); // Initialize ROS 2
|
| 24 |
+
await fastify.listen({ port: 4000, host: "0.0.0.0" });
|
| 25 |
+
console.log("[Backend] System Active on Port 4000");
|
| 26 |
+
} catch (err) {
|
| 27 |
+
fastify.log.error(err);
|
| 28 |
+
process.exit(1);
|
| 29 |
+
}
|
| 30 |
+
};
|
| 31 |
+
start();
|
apps/backend/src/modules/telemetry/telemetry.gateway.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import rclnodejs from "rclnodejs";
|
| 2 |
+
import { Server, Socket } from "socket.io";
|
| 3 |
+
|
| 4 |
+
// --- Shared Type Definition (Should be in shared-types, mirroring here for speed) ---
|
| 5 |
+
interface RobotTelemetry {
|
| 6 |
+
id: string;
|
| 7 |
+
x: number;
|
| 8 |
+
y: number;
|
| 9 |
+
theta: number; // yaw
|
| 10 |
+
battery: number;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export class TelemetryGateway {
|
| 14 |
+
private io: Server;
|
| 15 |
+
private node: rclnodejs.Node | null = null;
|
| 16 |
+
private robots: Map<string, RobotTelemetry> = new Map();
|
| 17 |
+
private lastUpdate: Map<string, number> = new Map();
|
| 18 |
+
|
| 19 |
+
constructor(io: Server) {
|
| 20 |
+
this.io = io;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
public async init() {
|
| 24 |
+
console.log("[Telemetry] Initializing ROS 2 Node...");
|
| 25 |
+
|
| 26 |
+
try {
|
| 27 |
+
await rclnodejs.init();
|
| 28 |
+
this.node = new rclnodejs.Node("backend_telemetry_bridge");
|
| 29 |
+
|
| 30 |
+
this.setupSubscribers();
|
| 31 |
+
|
| 32 |
+
// Start spinning
|
| 33 |
+
this.node.spin();
|
| 34 |
+
console.log("[Telemetry] ROS 2 Bridge Active.");
|
| 35 |
+
} catch (e) {
|
| 36 |
+
console.error("[Telemetry] Failed to init ROS 2:", e);
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
private setupSubscribers() {
|
| 41 |
+
if (!this.node) return;
|
| 42 |
+
|
| 43 |
+
// TODO: Dynamic discovery. For now, hardcode 'robot_1'.
|
| 44 |
+
const robotId = "robot_1";
|
| 45 |
+
|
| 46 |
+
// 1. Subscribe to Odom
|
| 47 |
+
this.node.createSubscription(
|
| 48 |
+
"nav_msgs/msg/Odometry",
|
| 49 |
+
"/odom", // Mock Robot publishes here
|
| 50 |
+
(msg: any) => this.handleOdom(robotId, msg),
|
| 51 |
+
);
|
| 52 |
+
|
| 53 |
+
// 2. Subscribe to Battery
|
| 54 |
+
this.node.createSubscription(
|
| 55 |
+
"sensor_msgs/msg/BatteryState",
|
| 56 |
+
"/battery",
|
| 57 |
+
(msg: any) => this.handleBattery(robotId, msg),
|
| 58 |
+
);
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
private handleOdom(robotId: string, msg: any) {
|
| 62 |
+
// Throttle: Only process if > 50ms has passed (20Hz max)
|
| 63 |
+
const now = Date.now();
|
| 64 |
+
const last = this.lastUpdate.get(robotId) || 0;
|
| 65 |
+
if (now - last < 50) return;
|
| 66 |
+
|
| 67 |
+
// Extract Position
|
| 68 |
+
const x = msg.pose.pose.position.x;
|
| 69 |
+
const y = msg.pose.pose.position.y;
|
| 70 |
+
|
| 71 |
+
// Extract Yaw (Quaternion to Euler - Simplified Z rotation)
|
| 72 |
+
const qz = msg.pose.pose.orientation.z;
|
| 73 |
+
const qw = msg.pose.pose.orientation.w;
|
| 74 |
+
const theta = 2 * Math.atan2(qz, qw);
|
| 75 |
+
|
| 76 |
+
// Update Internal State
|
| 77 |
+
const current = this.robots.get(robotId) || {
|
| 78 |
+
id: robotId,
|
| 79 |
+
x: 0,
|
| 80 |
+
y: 0,
|
| 81 |
+
theta: 0,
|
| 82 |
+
battery: 0,
|
| 83 |
+
};
|
| 84 |
+
current.x = x;
|
| 85 |
+
current.y = y;
|
| 86 |
+
current.theta = theta;
|
| 87 |
+
|
| 88 |
+
this.robots.set(robotId, current);
|
| 89 |
+
this.lastUpdate.set(robotId, now);
|
| 90 |
+
|
| 91 |
+
// Emit to Frontend
|
| 92 |
+
this.io.emit("telemetry", current);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
private handleBattery(robotId: string, msg: any) {
|
| 96 |
+
const current = this.robots.get(robotId);
|
| 97 |
+
if (current) {
|
| 98 |
+
current.battery = msg.percentage;
|
| 99 |
+
this.robots.set(robotId, current);
|
| 100 |
+
// We don't emit on battery alone, we wait for next odom pulse to bundle it
|
| 101 |
+
// or emit immediately if unrelated. Let's emit to be safe.
|
| 102 |
+
this.io.emit("telemetry", current);
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
apps/frontend/app/globals.css
CHANGED
|
@@ -1,26 +1,210 @@
|
|
| 1 |
-
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
}
|
| 7 |
|
| 8 |
@theme inline {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
--color-background: var(--background);
|
| 10 |
--color-foreground: var(--foreground);
|
| 11 |
-
--
|
| 12 |
-
--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
-
|
| 16 |
-
:
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
-
|
| 23 |
-
background:
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
|
|
|
| 1 |
+
/* Global CSS Reset & Enterprise Variables (Tailwind Extension) */
|
| 2 |
|
| 3 |
+
@plugin "tailwindcss-animate";
|
| 4 |
+
|
| 5 |
+
@custom-variant dark (&:is(.dark *));
|
| 6 |
+
@tailwind base;
|
| 7 |
+
@tailwind components;
|
| 8 |
+
@tailwind utilities;
|
| 9 |
+
|
| 10 |
+
@layer base {
|
| 11 |
+
:root {
|
| 12 |
+
/*
|
| 13 |
+
Semantic Color Palette - "Enterprise Gradient"
|
| 14 |
+
Avoiding generic AI blues/purples.
|
| 15 |
+
Using Slate/Zinc for neutrality + Strategic Accents.
|
| 16 |
+
*/
|
| 17 |
+
--bg-primary: 255 255 255; /* White */
|
| 18 |
+
--bg-secondary: 248 250 252; /* Slate-50 */
|
| 19 |
+
--bg-tertiary: 241 245 249; /* Slate-100 */
|
| 20 |
+
|
| 21 |
+
--text-primary: 15 23 42; /* Slate-900 */
|
| 22 |
+
--text-secondary: 71 85 105; /* Slate-600 */
|
| 23 |
+
--text-tertiary: 148 163 184; /* Slate-400 */
|
| 24 |
+
|
| 25 |
+
--border-primary: 226 232 240; /* Slate-200 */
|
| 26 |
+
--border-secondary: 203 213 225; /* Slate-300 */
|
| 27 |
+
|
| 28 |
+
/* Semantic Status - Not "Traffic Light", more muted/professional */
|
| 29 |
+
--success: 16 185 129; /* Emerald-500 */
|
| 30 |
+
--warning: 245 158 11; /* Amber-500 */
|
| 31 |
+
--error: 239 68 68; /* Red-500 */
|
| 32 |
+
--info: 59 130 246; /* Blue-500 */
|
| 33 |
+
|
| 34 |
+
/* Brand Identity - Deep Industrial Blue */
|
| 35 |
+
--brand-primary: 37 99 235; /* Blue-600 */
|
| 36 |
+
--brand-dark: 29 78 216; /* Blue-700 */
|
| 37 |
+
--brand-light: 96 165 250; /* Blue-400 */
|
| 38 |
+
|
| 39 |
+
/* Typography */
|
| 40 |
+
--font-sans: "Inter", system-ui, -apple-system, sans-serif;
|
| 41 |
+
--font-mono: "JetBrains Mono", monospace;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
[data-theme="dark"] {
|
| 45 |
+
--bg-primary: 15 23 42; /* Slate-900 */
|
| 46 |
+
--bg-secondary: 30 41 59; /* Slate-800 */
|
| 47 |
+
--bg-tertiary: 51 65 85; /* Slate-700 */
|
| 48 |
+
|
| 49 |
+
--text-primary: 248 250 252; /* Slate-50 */
|
| 50 |
+
--text-secondary: 148 163 184; /* Slate-400 */
|
| 51 |
+
--text-tertiary: 100 116 139; /* Slate-500 */
|
| 52 |
+
|
| 53 |
+
--border-primary: 51 65 85; /* Slate-700 */
|
| 54 |
+
--border-secondary: 71 85 105; /* Slate-600 */
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
body {
|
| 58 |
+
@apply bg-[rgb(var(--bg-secondary))] text-[rgb(var(--text-primary))] font-sans antialiased;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
/* Precision Typography for Data */
|
| 62 |
+
.font-mono-data {
|
| 63 |
+
font-variant-numeric: tabular-nums;
|
| 64 |
+
letter-spacing: -0.02em;
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/*
|
| 69 |
+
Component Tokens - Reusable Patterns
|
| 70 |
+
Defined here, imported via @apply classes
|
| 71 |
+
*/
|
| 72 |
+
@layer components {
|
| 73 |
+
.card-base {
|
| 74 |
+
@apply bg-[rgb(var(--bg-primary))] border border-[rgb(var(--border-primary))] rounded-lg shadow-sm p-4 transition-all;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.card-hover {
|
| 78 |
+
@apply hover:shadow-md hover:border-[rgb(var(--border-secondary))];
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.nav-link {
|
| 82 |
+
@apply flex items-center px-4 py-2 text-sm font-medium rounded-md text-[rgb(var(--text-secondary))] hover:bg-[rgb(var(--bg-tertiary))] hover:text-[rgb(var(--text-primary))] transition-colors;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.nav-link-active {
|
| 86 |
+
@apply bg-[rgb(var(--bg-secondary))] text-[rgb(var(--brand-primary))] border-l-4 border-[rgb(var(--brand-primary))];
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.control-btn {
|
| 90 |
+
@apply inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-[rgb(var(--brand-primary))] hover:bg-[rgb(var(--brand-dark))] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[rgb(var(--brand-light))];
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.data-value {
|
| 94 |
+
@apply font-mono-data text-lg font-semibold text-[rgb(var(--text-primary))];
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.data-label {
|
| 98 |
+
@apply text-xs font-bold uppercase tracking-wider text-[rgb(var(--text-tertiary))];
|
| 99 |
+
}
|
| 100 |
}
|
| 101 |
|
| 102 |
@theme inline {
|
| 103 |
+
--radius-sm: calc(var(--radius) - 4px);
|
| 104 |
+
--radius-md: calc(var(--radius) - 2px);
|
| 105 |
+
--radius-lg: var(--radius);
|
| 106 |
+
--radius-xl: calc(var(--radius) + 4px);
|
| 107 |
+
--radius-2xl: calc(var(--radius) + 8px);
|
| 108 |
+
--radius-3xl: calc(var(--radius) + 12px);
|
| 109 |
+
--radius-4xl: calc(var(--radius) + 16px);
|
| 110 |
--color-background: var(--background);
|
| 111 |
--color-foreground: var(--foreground);
|
| 112 |
+
--color-card: var(--card);
|
| 113 |
+
--color-card-foreground: var(--card-foreground);
|
| 114 |
+
--color-popover: var(--popover);
|
| 115 |
+
--color-popover-foreground: var(--popover-foreground);
|
| 116 |
+
--color-primary: var(--primary);
|
| 117 |
+
--color-primary-foreground: var(--primary-foreground);
|
| 118 |
+
--color-secondary: var(--secondary);
|
| 119 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
| 120 |
+
--color-muted: var(--muted);
|
| 121 |
+
--color-muted-foreground: var(--muted-foreground);
|
| 122 |
+
--color-accent: var(--accent);
|
| 123 |
+
--color-accent-foreground: var(--accent-foreground);
|
| 124 |
+
--color-destructive: var(--destructive);
|
| 125 |
+
--color-border: var(--border);
|
| 126 |
+
--color-input: var(--input);
|
| 127 |
+
--color-ring: var(--ring);
|
| 128 |
+
--color-chart-1: var(--chart-1);
|
| 129 |
+
--color-chart-2: var(--chart-2);
|
| 130 |
+
--color-chart-3: var(--chart-3);
|
| 131 |
+
--color-chart-4: var(--chart-4);
|
| 132 |
+
--color-chart-5: var(--chart-5);
|
| 133 |
+
--color-sidebar: var(--sidebar);
|
| 134 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
| 135 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
| 136 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
| 137 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
| 138 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
| 139 |
+
--color-sidebar-border: var(--sidebar-border);
|
| 140 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
| 141 |
}
|
| 142 |
|
| 143 |
+
:root {
|
| 144 |
+
--radius: 0.625rem;
|
| 145 |
+
--background: oklch(1 0 0);
|
| 146 |
+
--foreground: oklch(0.145 0 0);
|
| 147 |
+
--card: oklch(1 0 0);
|
| 148 |
+
--card-foreground: oklch(0.145 0 0);
|
| 149 |
+
--popover: oklch(1 0 0);
|
| 150 |
+
--popover-foreground: oklch(0.145 0 0);
|
| 151 |
+
--primary: oklch(0.205 0 0);
|
| 152 |
+
--primary-foreground: oklch(0.985 0 0);
|
| 153 |
+
--secondary: oklch(0.97 0 0);
|
| 154 |
+
--secondary-foreground: oklch(0.205 0 0);
|
| 155 |
+
--muted: oklch(0.97 0 0);
|
| 156 |
+
--muted-foreground: oklch(0.556 0 0);
|
| 157 |
+
--accent: oklch(0.97 0 0);
|
| 158 |
+
--accent-foreground: oklch(0.205 0 0);
|
| 159 |
+
--destructive: oklch(0.577 0.245 27.325);
|
| 160 |
+
--border: oklch(0.922 0 0);
|
| 161 |
+
--input: oklch(0.922 0 0);
|
| 162 |
+
--ring: oklch(0.708 0 0);
|
| 163 |
+
--chart-1: oklch(0.646 0.222 41.116);
|
| 164 |
+
--chart-2: oklch(0.6 0.118 184.704);
|
| 165 |
+
--chart-3: oklch(0.398 0.07 227.392);
|
| 166 |
+
--chart-4: oklch(0.828 0.189 84.429);
|
| 167 |
+
--chart-5: oklch(0.769 0.188 70.08);
|
| 168 |
+
--sidebar: oklch(0.985 0 0);
|
| 169 |
+
--sidebar-foreground: oklch(0.145 0 0);
|
| 170 |
+
--sidebar-primary: oklch(0.205 0 0);
|
| 171 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 172 |
+
--sidebar-accent: oklch(0.97 0 0);
|
| 173 |
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
| 174 |
+
--sidebar-border: oklch(0.922 0 0);
|
| 175 |
+
--sidebar-ring: oklch(0.708 0 0);
|
| 176 |
}
|
| 177 |
|
| 178 |
+
.dark {
|
| 179 |
+
--background: oklch(0.145 0 0);
|
| 180 |
+
--foreground: oklch(0.985 0 0);
|
| 181 |
+
--card: oklch(0.205 0 0);
|
| 182 |
+
--card-foreground: oklch(0.985 0 0);
|
| 183 |
+
--popover: oklch(0.205 0 0);
|
| 184 |
+
--popover-foreground: oklch(0.985 0 0);
|
| 185 |
+
--primary: oklch(0.922 0 0);
|
| 186 |
+
--primary-foreground: oklch(0.205 0 0);
|
| 187 |
+
--secondary: oklch(0.269 0 0);
|
| 188 |
+
--secondary-foreground: oklch(0.985 0 0);
|
| 189 |
+
--muted: oklch(0.269 0 0);
|
| 190 |
+
--muted-foreground: oklch(0.708 0 0);
|
| 191 |
+
--accent: oklch(0.269 0 0);
|
| 192 |
+
--accent-foreground: oklch(0.985 0 0);
|
| 193 |
+
--destructive: oklch(0.704 0.191 22.216);
|
| 194 |
+
--border: oklch(1 0 0 / 10%);
|
| 195 |
+
--input: oklch(1 0 0 / 15%);
|
| 196 |
+
--ring: oklch(0.556 0 0);
|
| 197 |
+
--chart-1: oklch(0.488 0.243 264.376);
|
| 198 |
+
--chart-2: oklch(0.696 0.17 162.48);
|
| 199 |
+
--chart-3: oklch(0.769 0.188 70.08);
|
| 200 |
+
--chart-4: oklch(0.627 0.265 303.9);
|
| 201 |
+
--chart-5: oklch(0.645 0.246 16.439);
|
| 202 |
+
--sidebar: oklch(0.205 0 0);
|
| 203 |
+
--sidebar-foreground: oklch(0.985 0 0);
|
| 204 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
| 205 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 206 |
+
--sidebar-accent: oklch(0.269 0 0);
|
| 207 |
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
| 208 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
| 209 |
+
--sidebar-ring: oklch(0.556 0 0);
|
| 210 |
}
|
apps/frontend/app/page.tsx
CHANGED
|
@@ -1,65 +1,129 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export default function Home() {
|
|
|
|
|
|
|
| 4 |
return (
|
| 5 |
-
<
|
| 6 |
-
<
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
width={100}
|
| 12 |
-
height={20}
|
| 13 |
-
priority
|
| 14 |
-
/>
|
| 15 |
-
<div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
|
| 16 |
-
<h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
|
| 17 |
-
To get started, edit the page.tsx file.
|
| 18 |
</h1>
|
| 19 |
-
<
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
className="
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
</
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
</div>
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
</div>
|
| 62 |
-
</
|
| 63 |
-
</
|
| 64 |
);
|
| 65 |
}
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import DashboardLayout from "@/components/layout/DashboardLayout";
|
| 4 |
+
import { useFleetTelemetry } from "@/hooks/useFleetTelemetry";
|
| 5 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 6 |
+
import { Badge } from "@/components/ui/badge";
|
| 7 |
+
import {
|
| 8 |
+
Table,
|
| 9 |
+
TableBody,
|
| 10 |
+
TableCell,
|
| 11 |
+
TableHead,
|
| 12 |
+
TableHeader,
|
| 13 |
+
TableRow,
|
| 14 |
+
} from "@/components/ui/table";
|
| 15 |
|
| 16 |
export default function Home() {
|
| 17 |
+
const { robots, isConnected } = useFleetTelemetry();
|
| 18 |
+
|
| 19 |
return (
|
| 20 |
+
<DashboardLayout>
|
| 21 |
+
<div className="space-y-6">
|
| 22 |
+
{/* Header */}
|
| 23 |
+
<div className="flex items-center justify-between">
|
| 24 |
+
<h1 className="text-2xl font-bold tracking-tight text-[rgb(var(--text-primary))]">
|
| 25 |
+
Fleet Overview
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
</h1>
|
| 27 |
+
<div className="flex items-center space-x-2">
|
| 28 |
+
<Badge variant={isConnected ? "default" : "destructive"}>
|
| 29 |
+
{isConnected ? "System Online" : "Disconnected"}
|
| 30 |
+
</Badge>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
{/* Updated KPI Grid */}
|
| 35 |
+
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
| 36 |
+
<Card>
|
| 37 |
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
| 38 |
+
<CardTitle className="text-sm font-medium">
|
| 39 |
+
Active Units
|
| 40 |
+
</CardTitle>
|
| 41 |
+
</CardHeader>
|
| 42 |
+
<CardContent>
|
| 43 |
+
<div className="text-2xl font-bold">{robots.length}</div>
|
| 44 |
+
<p className="text-xs text-muted-foreground">
|
| 45 |
+
+1 since last hour
|
| 46 |
+
</p>
|
| 47 |
+
</CardContent>
|
| 48 |
+
</Card>
|
| 49 |
+
|
| 50 |
+
<Card>
|
| 51 |
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
| 52 |
+
<CardTitle className="text-sm font-medium">Avg Battery</CardTitle>
|
| 53 |
+
</CardHeader>
|
| 54 |
+
<CardContent>
|
| 55 |
+
<div className="text-2xl font-bold">
|
| 56 |
+
{robots.length > 0
|
| 57 |
+
? Math.round(
|
| 58 |
+
robots.reduce((acc, r) => acc + r.battery, 0) /
|
| 59 |
+
robots.length,
|
| 60 |
+
)
|
| 61 |
+
: 0}
|
| 62 |
+
%
|
| 63 |
+
</div>
|
| 64 |
+
</CardContent>
|
| 65 |
+
</Card>
|
| 66 |
</div>
|
| 67 |
+
|
| 68 |
+
{/* Live Fleet Table */}
|
| 69 |
+
<div className="border rounded-lg overflow-hidden">
|
| 70 |
+
<Table>
|
| 71 |
+
<TableHeader>
|
| 72 |
+
<TableRow>
|
| 73 |
+
<TableHead>Robot ID</TableHead>
|
| 74 |
+
<TableHead>Status</TableHead>
|
| 75 |
+
<TableHead>Battery</TableHead>
|
| 76 |
+
<TableHead>Position (X, Y)</TableHead>
|
| 77 |
+
<TableHead className="text-right">Heading</TableHead>
|
| 78 |
+
</TableRow>
|
| 79 |
+
</TableHeader>
|
| 80 |
+
<TableBody>
|
| 81 |
+
{robots.map((robot) => (
|
| 82 |
+
<TableRow key={robot.id}>
|
| 83 |
+
<TableCell className="font-medium">{robot.id}</TableCell>
|
| 84 |
+
<TableCell>
|
| 85 |
+
<Badge
|
| 86 |
+
variant="outline"
|
| 87 |
+
className="bg-emerald-50 text-emerald-700 border-emerald-200"
|
| 88 |
+
>
|
| 89 |
+
Active
|
| 90 |
+
</Badge>
|
| 91 |
+
</TableCell>
|
| 92 |
+
<TableCell>
|
| 93 |
+
<div className="flex items-center">
|
| 94 |
+
<div className="w-16 h-2 bg-gray-200 rounded-full mr-2 overflow-hidden">
|
| 95 |
+
<div
|
| 96 |
+
className={`h-full ${robot.battery > 20 ? "bg-emerald-500" : "bg-red-500"}`}
|
| 97 |
+
style={{ width: `${robot.battery}%` }}
|
| 98 |
+
/>
|
| 99 |
+
</div>
|
| 100 |
+
<span className="text-xs font-mono">
|
| 101 |
+
{robot.battery.toFixed(1)}%
|
| 102 |
+
</span>
|
| 103 |
+
</div>
|
| 104 |
+
</TableCell>
|
| 105 |
+
<TableCell className="font-mono text-xs">
|
| 106 |
+
[{robot.x.toFixed(2)}, {robot.y.toFixed(2)}]
|
| 107 |
+
</TableCell>
|
| 108 |
+
<TableCell className="text-right font-mono text-xs">
|
| 109 |
+
{(robot.theta * (180 / Math.PI)).toFixed(1)}°
|
| 110 |
+
</TableCell>
|
| 111 |
+
</TableRow>
|
| 112 |
+
))}
|
| 113 |
+
{robots.length === 0 && (
|
| 114 |
+
<TableRow>
|
| 115 |
+
<TableCell
|
| 116 |
+
colSpan={5}
|
| 117 |
+
className="text-center text-muted-foreground py-8"
|
| 118 |
+
>
|
| 119 |
+
No robots detected. Start simulation to see data.
|
| 120 |
+
</TableCell>
|
| 121 |
+
</TableRow>
|
| 122 |
+
)}
|
| 123 |
+
</TableBody>
|
| 124 |
+
</Table>
|
| 125 |
</div>
|
| 126 |
+
</div>
|
| 127 |
+
</DashboardLayout>
|
| 128 |
);
|
| 129 |
}
|
apps/frontend/bun.lock
CHANGED
|
@@ -1,12 +1,21 @@
|
|
| 1 |
{
|
| 2 |
"lockfileVersion": 1,
|
|
|
|
| 3 |
"workspaces": {
|
| 4 |
"": {
|
| 5 |
"name": "frontend",
|
| 6 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
"next": "16.1.6",
|
| 8 |
"react": "19.2.3",
|
| 9 |
"react-dom": "19.2.3",
|
|
|
|
|
|
|
|
|
|
| 10 |
},
|
| 11 |
"devDependencies": {
|
| 12 |
"@tailwindcss/postcss": "^4",
|
|
@@ -15,6 +24,7 @@
|
|
| 15 |
"@types/react-dom": "^19",
|
| 16 |
"eslint": "^9",
|
| 17 |
"eslint-config-next": "16.1.6",
|
|
|
|
| 18 |
"tailwindcss": "^4",
|
| 19 |
"typescript": "^5",
|
| 20 |
},
|
|
@@ -181,8 +191,26 @@
|
|
| 181 |
|
| 182 |
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
| 183 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
| 185 |
|
|
|
|
|
|
|
| 186 |
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
| 187 |
|
| 188 |
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
|
@@ -347,8 +375,12 @@
|
|
| 347 |
|
| 348 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
| 349 |
|
|
|
|
|
|
|
| 350 |
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
| 351 |
|
|
|
|
|
|
|
| 352 |
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
| 353 |
|
| 354 |
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
|
@@ -387,6 +419,10 @@
|
|
| 387 |
|
| 388 |
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
| 389 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
| 391 |
|
| 392 |
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
|
@@ -635,6 +671,8 @@
|
|
| 635 |
|
| 636 |
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
| 637 |
|
|
|
|
|
|
|
| 638 |
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
| 639 |
|
| 640 |
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
|
@@ -745,6 +783,8 @@
|
|
| 745 |
|
| 746 |
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
| 747 |
|
|
|
|
|
|
|
| 748 |
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
| 749 |
|
| 750 |
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
|
@@ -759,6 +799,10 @@
|
|
| 759 |
|
| 760 |
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
| 761 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
| 763 |
|
| 764 |
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
|
|
@@ -787,8 +831,12 @@
|
|
| 787 |
|
| 788 |
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
| 789 |
|
|
|
|
|
|
|
| 790 |
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
| 791 |
|
|
|
|
|
|
|
| 792 |
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
| 793 |
|
| 794 |
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
|
@@ -825,6 +873,8 @@
|
|
| 825 |
|
| 826 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
| 827 |
|
|
|
|
|
|
|
| 828 |
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
| 829 |
|
| 830 |
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
|
@@ -837,6 +887,10 @@
|
|
| 837 |
|
| 838 |
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
| 839 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
| 841 |
|
| 842 |
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
|
@@ -887,6 +941,8 @@
|
|
| 887 |
|
| 888 |
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
| 889 |
|
|
|
|
|
|
|
| 890 |
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
| 891 |
|
| 892 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
|
|
|
| 1 |
{
|
| 2 |
"lockfileVersion": 1,
|
| 3 |
+
"configVersion": 0,
|
| 4 |
"workspaces": {
|
| 5 |
"": {
|
| 6 |
"name": "frontend",
|
| 7 |
"dependencies": {
|
| 8 |
+
"@radix-ui/react-avatar": "^1.1.11",
|
| 9 |
+
"@radix-ui/react-slot": "^1.2.4",
|
| 10 |
+
"class-variance-authority": "^0.7.1",
|
| 11 |
+
"clsx": "^2.1.1",
|
| 12 |
+
"lucide-react": "^0.574.0",
|
| 13 |
"next": "16.1.6",
|
| 14 |
"react": "19.2.3",
|
| 15 |
"react-dom": "19.2.3",
|
| 16 |
+
"socket.io-client": "^4.8.3",
|
| 17 |
+
"tailwind-merge": "^3.4.1",
|
| 18 |
+
"tailwindcss-animate": "^1.0.7",
|
| 19 |
},
|
| 20 |
"devDependencies": {
|
| 21 |
"@tailwindcss/postcss": "^4",
|
|
|
|
| 24 |
"@types/react-dom": "^19",
|
| 25 |
"eslint": "^9",
|
| 26 |
"eslint-config-next": "16.1.6",
|
| 27 |
+
"shadcn-ui": "^0.9.5",
|
| 28 |
"tailwindcss": "^4",
|
| 29 |
"typescript": "^5",
|
| 30 |
},
|
|
|
|
| 191 |
|
| 192 |
"@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
|
| 193 |
|
| 194 |
+
"@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="],
|
| 195 |
+
|
| 196 |
+
"@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
|
| 197 |
+
|
| 198 |
+
"@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="],
|
| 199 |
+
|
| 200 |
+
"@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="],
|
| 201 |
+
|
| 202 |
+
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
|
| 203 |
+
|
| 204 |
+
"@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
|
| 205 |
+
|
| 206 |
+
"@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="],
|
| 207 |
+
|
| 208 |
+
"@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
|
| 209 |
+
|
| 210 |
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
|
| 211 |
|
| 212 |
+
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
| 213 |
+
|
| 214 |
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
|
| 215 |
|
| 216 |
"@tailwindcss/node": ["@tailwindcss/node@4.1.18", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.18" } }, "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ=="],
|
|
|
|
| 375 |
|
| 376 |
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
| 377 |
|
| 378 |
+
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
|
| 379 |
+
|
| 380 |
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
|
| 381 |
|
| 382 |
+
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
| 383 |
+
|
| 384 |
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
| 385 |
|
| 386 |
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
|
|
|
| 419 |
|
| 420 |
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
| 421 |
|
| 422 |
+
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
|
| 423 |
+
|
| 424 |
+
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
|
| 425 |
+
|
| 426 |
"enhanced-resolve": ["enhanced-resolve@5.19.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg=="],
|
| 427 |
|
| 428 |
"es-abstract": ["es-abstract@1.24.1", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw=="],
|
|
|
|
| 671 |
|
| 672 |
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
| 673 |
|
| 674 |
+
"lucide-react": ["lucide-react@0.574.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dJ8xb5juiZVIbdSn3HTyHsjjIwUwZ4FNwV0RtYDScOyySOeie1oXZTymST6YPJ4Qwt3Po8g4quhYl4OxtACiuQ=="],
|
| 675 |
+
|
| 676 |
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
| 677 |
|
| 678 |
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
|
|
|
| 783 |
|
| 784 |
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
|
| 785 |
|
| 786 |
+
"shadcn-ui": ["shadcn-ui@0.9.5", "", { "dependencies": { "chalk": "^5.4.1" }, "bin": { "shadcn-ui": "dist/index.js" } }, "sha512-dsBQWpdLLYCdSdmvOmu53nJhhWnQD1OiblhuhkI4rPYxPKTyfbmZ2NTJHWMu1fXN9PTfN6IVK5vvh+BrjHJx2g=="],
|
| 787 |
+
|
| 788 |
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
| 789 |
|
| 790 |
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
|
|
|
| 799 |
|
| 800 |
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
| 801 |
|
| 802 |
+
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
|
| 803 |
+
|
| 804 |
+
"socket.io-parser": ["socket.io-parser@4.2.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ=="],
|
| 805 |
+
|
| 806 |
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
| 807 |
|
| 808 |
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
|
|
|
|
| 831 |
|
| 832 |
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
| 833 |
|
| 834 |
+
"tailwind-merge": ["tailwind-merge@3.4.1", "", {}, "sha512-2OA0rFqWOkITEAOFWSBSApYkDeH9t2B3XSJuI4YztKBzK3mX0737A2qtxDZ7xkw9Zfh0bWl+r34sF3HXV+Ig7Q=="],
|
| 835 |
+
|
| 836 |
"tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="],
|
| 837 |
|
| 838 |
+
"tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
|
| 839 |
+
|
| 840 |
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
| 841 |
|
| 842 |
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
|
|
|
| 873 |
|
| 874 |
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
| 875 |
|
| 876 |
+
"use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
|
| 877 |
+
|
| 878 |
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
| 879 |
|
| 880 |
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
|
|
|
| 887 |
|
| 888 |
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
| 889 |
|
| 890 |
+
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
|
| 891 |
+
|
| 892 |
+
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
|
| 893 |
+
|
| 894 |
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
|
| 895 |
|
| 896 |
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
|
|
|
| 941 |
|
| 942 |
"next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
|
| 943 |
|
| 944 |
+
"shadcn-ui/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
| 945 |
+
|
| 946 |
"sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
| 947 |
|
| 948 |
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
apps/frontend/components.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "new-york",
|
| 4 |
+
"rsc": true,
|
| 5 |
+
"tsx": true,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "tailwind.config.js",
|
| 8 |
+
"css": "app/globals.css",
|
| 9 |
+
"baseColor": "neutral",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"iconLibrary": "lucide",
|
| 14 |
+
"rtl": false,
|
| 15 |
+
"aliases": {
|
| 16 |
+
"components": "@/components",
|
| 17 |
+
"utils": "@/lib/utils",
|
| 18 |
+
"ui": "@/components/ui",
|
| 19 |
+
"lib": "@/lib",
|
| 20 |
+
"hooks": "@/hooks"
|
| 21 |
+
},
|
| 22 |
+
"registries": {}
|
| 23 |
+
}
|
apps/frontend/components/layout/DashboardLayout.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Sidebar from "./Sidebar";
|
| 2 |
+
|
| 3 |
+
export default function DashboardLayout({
|
| 4 |
+
children,
|
| 5 |
+
}: {
|
| 6 |
+
children: React.ReactNode;
|
| 7 |
+
}) {
|
| 8 |
+
return (
|
| 9 |
+
<div className="flex h-screen w-full bg-[rgb(var(--bg-secondary))] text-[rgb(var(--text-primary))]">
|
| 10 |
+
{/* Fixed Sidebar */}
|
| 11 |
+
<aside className="fixed inset-y-0 left-0 z-50 w-64">
|
| 12 |
+
<Sidebar />
|
| 13 |
+
</aside>
|
| 14 |
+
|
| 15 |
+
{/* Main Content Area */}
|
| 16 |
+
<main className="pl-64 flex-1 h-full overflow-y-auto">
|
| 17 |
+
<div className="max-w-7xl mx-auto p-8">{children}</div>
|
| 18 |
+
</main>
|
| 19 |
+
</div>
|
| 20 |
+
);
|
| 21 |
+
}
|
apps/frontend/components/layout/Sidebar.tsx
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
|
| 5 |
+
interface NavItem {
|
| 6 |
+
id: string;
|
| 7 |
+
label: string;
|
| 8 |
+
href: string;
|
| 9 |
+
icon?: string; // Replace with proper Icon component later
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
const ROBOT_NAV: NavItem[] = [
|
| 13 |
+
{ id: "robot_1", label: "Robot 1 (AMR)", href: "/robots/robot_1" },
|
| 14 |
+
{ id: "robot_2", label: "Robot 2 (Arm)", href: "/robots/robot_2" },
|
| 15 |
+
];
|
| 16 |
+
|
| 17 |
+
const MAIN_NAV: NavItem[] = [
|
| 18 |
+
{ id: "overview", label: "Fleet Overview", href: "/" },
|
| 19 |
+
{ id: "ml-vision", label: "ML Vision", href: "/ml-vision" },
|
| 20 |
+
];
|
| 21 |
+
|
| 22 |
+
export default function Sidebar() {
|
| 23 |
+
return (
|
| 24 |
+
<div className="w-64 h-full bg-[rgb(var(--bg-primary))] border-r border-[rgb(var(--border-primary))] flex flex-col">
|
| 25 |
+
{/* Brand Header */}
|
| 26 |
+
<div className="p-6 border-b border-[rgb(var(--border-primary))]">
|
| 27 |
+
<h1 className="text-xl font-bold tracking-tight text-[rgb(var(--text-primary))]">
|
| 28 |
+
Omniverse
|
| 29 |
+
<span className="text-[rgb(var(--brand-primary))]">Fleet</span>
|
| 30 |
+
</h1>
|
| 31 |
+
<p className="text-xs text-[rgb(var(--text-tertiary))] mt-1">
|
| 32 |
+
Industrial Control v1.0
|
| 33 |
+
</p>
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
{/* Main Navigation */}
|
| 37 |
+
<nav className="flex-1 p-4 space-y-6 overflow-y-auto">
|
| 38 |
+
{/* Section: Platform */}
|
| 39 |
+
<div>
|
| 40 |
+
<h2 className="px-4 text-xs font-semibold text-[rgb(var(--text-tertiary))] uppercase tracking-wider mb-2">
|
| 41 |
+
Platform
|
| 42 |
+
</h2>
|
| 43 |
+
<div className="space-y-1">
|
| 44 |
+
{MAIN_NAV.map((item) => (
|
| 45 |
+
<Link key={item.id} href={item.href} className="nav-link">
|
| 46 |
+
{item.label}
|
| 47 |
+
</Link>
|
| 48 |
+
))}
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
{/* Section: Robots */}
|
| 53 |
+
<div>
|
| 54 |
+
<div className="flex items-center justify-between px-4 mb-2">
|
| 55 |
+
<h2 className="text-xs font-semibold text-[rgb(var(--text-tertiary))] uppercase tracking-wider">
|
| 56 |
+
Active Units
|
| 57 |
+
</h2>
|
| 58 |
+
<span className="px-2 py-0.5 text-[10px] bg-emerald-100 text-emerald-800 rounded-full font-mono">
|
| 59 |
+
{ROBOT_NAV.length} ONLINE
|
| 60 |
+
</span>
|
| 61 |
+
</div>
|
| 62 |
+
|
| 63 |
+
<div className="space-y-1">
|
| 64 |
+
{ROBOT_NAV.map((robot) => (
|
| 65 |
+
<Link key={robot.id} href={robot.href} className="nav-link group">
|
| 66 |
+
<span className="w-2 h-2 rounded-full bg-emerald-500 mr-3 group-hover:animate-pulse"></span>
|
| 67 |
+
{robot.label}
|
| 68 |
+
</Link>
|
| 69 |
+
))}
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
</nav>
|
| 73 |
+
|
| 74 |
+
{/* Footer / User Profile */}
|
| 75 |
+
<div className="p-4 border-t border-[rgb(var(--border-primary))] bg-[rgb(var(--bg-secondary))]">
|
| 76 |
+
<div className="flex items-center">
|
| 77 |
+
<div className="w-8 h-8 rounded-full bg-[rgb(var(--brand-primary))] flex items-center justify-center text-white font-bold">
|
| 78 |
+
OP
|
| 79 |
+
</div>
|
| 80 |
+
<div className="ml-3">
|
| 81 |
+
<p className="text-sm font-medium text-[rgb(var(--text-primary))]">
|
| 82 |
+
Operator
|
| 83 |
+
</p>
|
| 84 |
+
<p className="text-xs text-[rgb(var(--text-secondary))]">
|
| 85 |
+
System Admin
|
| 86 |
+
</p>
|
| 87 |
+
</div>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
);
|
| 92 |
+
}
|
apps/frontend/components/ui/avatar.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client"
|
| 2 |
+
|
| 3 |
+
import * as React from "react"
|
| 4 |
+
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
| 5 |
+
|
| 6 |
+
import { cn } from "@/lib/utils"
|
| 7 |
+
|
| 8 |
+
const Avatar = React.forwardRef<
|
| 9 |
+
React.ElementRef<typeof AvatarPrimitive.Root>,
|
| 10 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
| 11 |
+
>(({ className, ...props }, ref) => (
|
| 12 |
+
<AvatarPrimitive.Root
|
| 13 |
+
ref={ref}
|
| 14 |
+
className={cn(
|
| 15 |
+
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
| 16 |
+
className
|
| 17 |
+
)}
|
| 18 |
+
{...props}
|
| 19 |
+
/>
|
| 20 |
+
))
|
| 21 |
+
Avatar.displayName = AvatarPrimitive.Root.displayName
|
| 22 |
+
|
| 23 |
+
const AvatarImage = React.forwardRef<
|
| 24 |
+
React.ElementRef<typeof AvatarPrimitive.Image>,
|
| 25 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
| 26 |
+
>(({ className, ...props }, ref) => (
|
| 27 |
+
<AvatarPrimitive.Image
|
| 28 |
+
ref={ref}
|
| 29 |
+
className={cn("aspect-square h-full w-full", className)}
|
| 30 |
+
{...props}
|
| 31 |
+
/>
|
| 32 |
+
))
|
| 33 |
+
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
| 34 |
+
|
| 35 |
+
const AvatarFallback = React.forwardRef<
|
| 36 |
+
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
| 37 |
+
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
| 38 |
+
>(({ className, ...props }, ref) => (
|
| 39 |
+
<AvatarPrimitive.Fallback
|
| 40 |
+
ref={ref}
|
| 41 |
+
className={cn(
|
| 42 |
+
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
| 43 |
+
className
|
| 44 |
+
)}
|
| 45 |
+
{...props}
|
| 46 |
+
/>
|
| 47 |
+
))
|
| 48 |
+
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
| 49 |
+
|
| 50 |
+
export { Avatar, AvatarImage, AvatarFallback }
|
apps/frontend/components/ui/badge.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const badgeVariants = cva(
|
| 7 |
+
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
| 8 |
+
{
|
| 9 |
+
variants: {
|
| 10 |
+
variant: {
|
| 11 |
+
default:
|
| 12 |
+
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
| 13 |
+
secondary:
|
| 14 |
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 15 |
+
destructive:
|
| 16 |
+
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
| 17 |
+
outline: "text-foreground",
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
defaultVariants: {
|
| 21 |
+
variant: "default",
|
| 22 |
+
},
|
| 23 |
+
}
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
export interface BadgeProps
|
| 27 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
| 28 |
+
VariantProps<typeof badgeVariants> {}
|
| 29 |
+
|
| 30 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
| 31 |
+
return (
|
| 32 |
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
| 33 |
+
)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
export { Badge, badgeVariants }
|
apps/frontend/components/ui/button.tsx
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { Slot } from "@radix-ui/react-slot"
|
| 3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const buttonVariants = cva(
|
| 8 |
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default:
|
| 13 |
+
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
| 14 |
+
destructive:
|
| 15 |
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
| 16 |
+
outline:
|
| 17 |
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
| 18 |
+
secondary:
|
| 19 |
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
| 20 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default: "h-9 px-4 py-2",
|
| 25 |
+
sm: "h-8 rounded-md px-3 text-xs",
|
| 26 |
+
lg: "h-10 rounded-md px-8",
|
| 27 |
+
icon: "h-9 w-9",
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
defaultVariants: {
|
| 31 |
+
variant: "default",
|
| 32 |
+
size: "default",
|
| 33 |
+
},
|
| 34 |
+
}
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
export interface ButtonProps
|
| 38 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
| 39 |
+
VariantProps<typeof buttonVariants> {
|
| 40 |
+
asChild?: boolean
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
| 44 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
| 45 |
+
const Comp = asChild ? Slot : "button"
|
| 46 |
+
return (
|
| 47 |
+
<Comp
|
| 48 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 49 |
+
ref={ref}
|
| 50 |
+
{...props}
|
| 51 |
+
/>
|
| 52 |
+
)
|
| 53 |
+
}
|
| 54 |
+
)
|
| 55 |
+
Button.displayName = "Button"
|
| 56 |
+
|
| 57 |
+
export { Button, buttonVariants }
|
apps/frontend/components/ui/card.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Card = React.forwardRef<
|
| 6 |
+
HTMLDivElement,
|
| 7 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 8 |
+
>(({ className, ...props }, ref) => (
|
| 9 |
+
<div
|
| 10 |
+
ref={ref}
|
| 11 |
+
className={cn(
|
| 12 |
+
"rounded-xl border bg-card text-card-foreground shadow",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
{...props}
|
| 16 |
+
/>
|
| 17 |
+
))
|
| 18 |
+
Card.displayName = "Card"
|
| 19 |
+
|
| 20 |
+
const CardHeader = React.forwardRef<
|
| 21 |
+
HTMLDivElement,
|
| 22 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 23 |
+
>(({ className, ...props }, ref) => (
|
| 24 |
+
<div
|
| 25 |
+
ref={ref}
|
| 26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
| 27 |
+
{...props}
|
| 28 |
+
/>
|
| 29 |
+
))
|
| 30 |
+
CardHeader.displayName = "CardHeader"
|
| 31 |
+
|
| 32 |
+
const CardTitle = React.forwardRef<
|
| 33 |
+
HTMLDivElement,
|
| 34 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 35 |
+
>(({ className, ...props }, ref) => (
|
| 36 |
+
<div
|
| 37 |
+
ref={ref}
|
| 38 |
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
| 39 |
+
{...props}
|
| 40 |
+
/>
|
| 41 |
+
))
|
| 42 |
+
CardTitle.displayName = "CardTitle"
|
| 43 |
+
|
| 44 |
+
const CardDescription = React.forwardRef<
|
| 45 |
+
HTMLDivElement,
|
| 46 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 47 |
+
>(({ className, ...props }, ref) => (
|
| 48 |
+
<div
|
| 49 |
+
ref={ref}
|
| 50 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 51 |
+
{...props}
|
| 52 |
+
/>
|
| 53 |
+
))
|
| 54 |
+
CardDescription.displayName = "CardDescription"
|
| 55 |
+
|
| 56 |
+
const CardContent = React.forwardRef<
|
| 57 |
+
HTMLDivElement,
|
| 58 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 59 |
+
>(({ className, ...props }, ref) => (
|
| 60 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
| 61 |
+
))
|
| 62 |
+
CardContent.displayName = "CardContent"
|
| 63 |
+
|
| 64 |
+
const CardFooter = React.forwardRef<
|
| 65 |
+
HTMLDivElement,
|
| 66 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 67 |
+
>(({ className, ...props }, ref) => (
|
| 68 |
+
<div
|
| 69 |
+
ref={ref}
|
| 70 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
| 71 |
+
{...props}
|
| 72 |
+
/>
|
| 73 |
+
))
|
| 74 |
+
CardFooter.displayName = "CardFooter"
|
| 75 |
+
|
| 76 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
apps/frontend/components/ui/table.tsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Table = React.forwardRef<
|
| 6 |
+
HTMLTableElement,
|
| 7 |
+
React.HTMLAttributes<HTMLTableElement>
|
| 8 |
+
>(({ className, ...props }, ref) => (
|
| 9 |
+
<div className="relative w-full overflow-auto">
|
| 10 |
+
<table
|
| 11 |
+
ref={ref}
|
| 12 |
+
className={cn("w-full caption-bottom text-sm", className)}
|
| 13 |
+
{...props}
|
| 14 |
+
/>
|
| 15 |
+
</div>
|
| 16 |
+
))
|
| 17 |
+
Table.displayName = "Table"
|
| 18 |
+
|
| 19 |
+
const TableHeader = React.forwardRef<
|
| 20 |
+
HTMLTableSectionElement,
|
| 21 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 22 |
+
>(({ className, ...props }, ref) => (
|
| 23 |
+
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
| 24 |
+
))
|
| 25 |
+
TableHeader.displayName = "TableHeader"
|
| 26 |
+
|
| 27 |
+
const TableBody = React.forwardRef<
|
| 28 |
+
HTMLTableSectionElement,
|
| 29 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 30 |
+
>(({ className, ...props }, ref) => (
|
| 31 |
+
<tbody
|
| 32 |
+
ref={ref}
|
| 33 |
+
className={cn("[&_tr:last-child]:border-0", className)}
|
| 34 |
+
{...props}
|
| 35 |
+
/>
|
| 36 |
+
))
|
| 37 |
+
TableBody.displayName = "TableBody"
|
| 38 |
+
|
| 39 |
+
const TableFooter = React.forwardRef<
|
| 40 |
+
HTMLTableSectionElement,
|
| 41 |
+
React.HTMLAttributes<HTMLTableSectionElement>
|
| 42 |
+
>(({ className, ...props }, ref) => (
|
| 43 |
+
<tfoot
|
| 44 |
+
ref={ref}
|
| 45 |
+
className={cn(
|
| 46 |
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
| 47 |
+
className
|
| 48 |
+
)}
|
| 49 |
+
{...props}
|
| 50 |
+
/>
|
| 51 |
+
))
|
| 52 |
+
TableFooter.displayName = "TableFooter"
|
| 53 |
+
|
| 54 |
+
const TableRow = React.forwardRef<
|
| 55 |
+
HTMLTableRowElement,
|
| 56 |
+
React.HTMLAttributes<HTMLTableRowElement>
|
| 57 |
+
>(({ className, ...props }, ref) => (
|
| 58 |
+
<tr
|
| 59 |
+
ref={ref}
|
| 60 |
+
className={cn(
|
| 61 |
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
| 62 |
+
className
|
| 63 |
+
)}
|
| 64 |
+
{...props}
|
| 65 |
+
/>
|
| 66 |
+
))
|
| 67 |
+
TableRow.displayName = "TableRow"
|
| 68 |
+
|
| 69 |
+
const TableHead = React.forwardRef<
|
| 70 |
+
HTMLTableCellElement,
|
| 71 |
+
React.ThHTMLAttributes<HTMLTableCellElement>
|
| 72 |
+
>(({ className, ...props }, ref) => (
|
| 73 |
+
<th
|
| 74 |
+
ref={ref}
|
| 75 |
+
className={cn(
|
| 76 |
+
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
| 77 |
+
className
|
| 78 |
+
)}
|
| 79 |
+
{...props}
|
| 80 |
+
/>
|
| 81 |
+
))
|
| 82 |
+
TableHead.displayName = "TableHead"
|
| 83 |
+
|
| 84 |
+
const TableCell = React.forwardRef<
|
| 85 |
+
HTMLTableCellElement,
|
| 86 |
+
React.TdHTMLAttributes<HTMLTableCellElement>
|
| 87 |
+
>(({ className, ...props }, ref) => (
|
| 88 |
+
<td
|
| 89 |
+
ref={ref}
|
| 90 |
+
className={cn(
|
| 91 |
+
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
| 92 |
+
className
|
| 93 |
+
)}
|
| 94 |
+
{...props}
|
| 95 |
+
/>
|
| 96 |
+
))
|
| 97 |
+
TableCell.displayName = "TableCell"
|
| 98 |
+
|
| 99 |
+
const TableCaption = React.forwardRef<
|
| 100 |
+
HTMLTableCaptionElement,
|
| 101 |
+
React.HTMLAttributes<HTMLTableCaptionElement>
|
| 102 |
+
>(({ className, ...props }, ref) => (
|
| 103 |
+
<caption
|
| 104 |
+
ref={ref}
|
| 105 |
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
| 106 |
+
{...props}
|
| 107 |
+
/>
|
| 108 |
+
))
|
| 109 |
+
TableCaption.displayName = "TableCaption"
|
| 110 |
+
|
| 111 |
+
export {
|
| 112 |
+
Table,
|
| 113 |
+
TableHeader,
|
| 114 |
+
TableBody,
|
| 115 |
+
TableFooter,
|
| 116 |
+
TableHead,
|
| 117 |
+
TableRow,
|
| 118 |
+
TableCell,
|
| 119 |
+
TableCaption,
|
| 120 |
+
}
|
apps/frontend/hooks/useFleetTelemetry.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useEffect, useState, useRef } from "react";
|
| 4 |
+
import { io, Socket } from "socket.io-client";
|
| 5 |
+
|
| 6 |
+
// Shared type (Should be in shared-types in production)
|
| 7 |
+
export interface RobotState {
|
| 8 |
+
id: string;
|
| 9 |
+
x: number;
|
| 10 |
+
y: number;
|
| 11 |
+
theta: number;
|
| 12 |
+
battery: number;
|
| 13 |
+
status: "ONLINE" | "OFFLINE";
|
| 14 |
+
lastSeen: number;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
const SOCKET_URL = "http://localhost:4000";
|
| 18 |
+
|
| 19 |
+
export function useFleetTelemetry() {
|
| 20 |
+
const [robots, setRobots] = useState<Map<string, RobotState>>(new Map());
|
| 21 |
+
const socketRef = useRef<Socket | null>(null);
|
| 22 |
+
const [isConnected, setIsConnected] = useState(false);
|
| 23 |
+
|
| 24 |
+
useEffect(() => {
|
| 25 |
+
// 1. Initialize Socket
|
| 26 |
+
const socket = io(SOCKET_URL, {
|
| 27 |
+
transports: ["websocket"],
|
| 28 |
+
reconnectionAttempts: 5,
|
| 29 |
+
});
|
| 30 |
+
socketRef.current = socket;
|
| 31 |
+
|
| 32 |
+
// 2. Connection Handlers
|
| 33 |
+
socket.on("connect", () => {
|
| 34 |
+
console.log("[Frontend] Connected to Telemetry Gateway");
|
| 35 |
+
setIsConnected(true);
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
socket.on("disconnect", () => {
|
| 39 |
+
console.log("[Frontend] Disconnected");
|
| 40 |
+
setIsConnected(false);
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
// 3. Telemetry Listener
|
| 44 |
+
socket.on("telemetry", (data: any) => {
|
| 45 |
+
setRobots((prev) => {
|
| 46 |
+
const next = new Map(prev);
|
| 47 |
+
next.set(data.id, {
|
| 48 |
+
...data,
|
| 49 |
+
status: "ONLINE",
|
| 50 |
+
lastSeen: Date.now(),
|
| 51 |
+
});
|
| 52 |
+
return next;
|
| 53 |
+
});
|
| 54 |
+
});
|
| 55 |
+
|
| 56 |
+
return () => {
|
| 57 |
+
socket.disconnect();
|
| 58 |
+
};
|
| 59 |
+
}, []);
|
| 60 |
+
|
| 61 |
+
return {
|
| 62 |
+
robots: Array.from(robots.values()),
|
| 63 |
+
isConnected,
|
| 64 |
+
};
|
| 65 |
+
}
|
apps/frontend/package.json
CHANGED
|
@@ -9,9 +9,17 @@
|
|
| 9 |
"lint": "eslint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
"next": "16.1.6",
|
| 13 |
"react": "19.2.3",
|
| 14 |
-
"react-dom": "19.2.3"
|
|
|
|
|
|
|
|
|
|
| 15 |
},
|
| 16 |
"devDependencies": {
|
| 17 |
"@tailwindcss/postcss": "^4",
|
|
@@ -20,6 +28,7 @@
|
|
| 20 |
"@types/react-dom": "^19",
|
| 21 |
"eslint": "^9",
|
| 22 |
"eslint-config-next": "16.1.6",
|
|
|
|
| 23 |
"tailwindcss": "^4",
|
| 24 |
"typescript": "^5"
|
| 25 |
},
|
|
|
|
| 9 |
"lint": "eslint"
|
| 10 |
},
|
| 11 |
"dependencies": {
|
| 12 |
+
"@radix-ui/react-avatar": "^1.1.11",
|
| 13 |
+
"@radix-ui/react-slot": "^1.2.4",
|
| 14 |
+
"class-variance-authority": "^0.7.1",
|
| 15 |
+
"clsx": "^2.1.1",
|
| 16 |
+
"lucide-react": "^0.574.0",
|
| 17 |
"next": "16.1.6",
|
| 18 |
"react": "19.2.3",
|
| 19 |
+
"react-dom": "19.2.3",
|
| 20 |
+
"socket.io-client": "^4.8.3",
|
| 21 |
+
"tailwind-merge": "^3.4.1",
|
| 22 |
+
"tailwindcss-animate": "^1.0.7"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
| 25 |
"@tailwindcss/postcss": "^4",
|
|
|
|
| 28 |
"@types/react-dom": "^19",
|
| 29 |
"eslint": "^9",
|
| 30 |
"eslint-config-next": "16.1.6",
|
| 31 |
+
"shadcn-ui": "^0.9.5",
|
| 32 |
"tailwindcss": "^4",
|
| 33 |
"typescript": "^5"
|
| 34 |
},
|
apps/frontend/tailwind.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
module.exports = {
|
| 2 |
+
content: [
|
| 3 |
+
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
|
| 4 |
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
| 5 |
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
| 6 |
+
],
|
| 7 |
+
theme: {
|
| 8 |
+
extend: {
|
| 9 |
+
colors: {
|
| 10 |
+
// We use CSS variables for easy theming
|
| 11 |
+
},
|
| 12 |
+
fontFamily: {
|
| 13 |
+
sans: ["var(--font-sans)"],
|
| 14 |
+
mono: ["var(--font-mono)"],
|
| 15 |
+
},
|
| 16 |
+
},
|
| 17 |
+
},
|
| 18 |
+
plugins: [],
|
| 19 |
+
};
|
docs/planning/RFC-001-vertical-architecture.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architectural Start: Vertical Feature Slicing Strategy
|
| 2 |
+
|
| 3 |
+
## 1. The Core Philosophy
|
| 4 |
+
|
| 5 |
+
We do **not** build layers (all DB, then all API). We build **Vertical Features**.
|
| 6 |
+
A vertical feature is a self-contained slice of functionality that cuts through the Frontend, Backend, ML, and Robotics layers.
|
| 7 |
+
|
| 8 |
+
## 2. Decoupling Strategy
|
| 9 |
+
|
| 10 |
+
### Frontend (`apps/frontend`)
|
| 11 |
+
|
| 12 |
+
- **Structure**: `components/features/[feature_name]` (e.g., `components/features/telemetry`, `components/features/map`).
|
| 13 |
+
- **Rule**: A component should be isolated. It imports its own data hooks.
|
| 14 |
+
- **Robot Isolation**: Use dynamic routing `/robots/[id]/dashboard`. Components take `robotId` as a prop.
|
| 15 |
+
|
| 16 |
+
### Backend (`apps/backend`)
|
| 17 |
+
|
| 18 |
+
- **Structure**: `src/modules/[module_name]` (e.g., `src/modules/fleet`, `src/modules/auth`).
|
| 19 |
+
- **Rule**: A module contains its own Service, Controller, and DTOs. Modules communicate via Event Bus or Public Interface.
|
| 20 |
+
|
| 21 |
+
### ROS 2 (`robotics/ros2_ws`)
|
| 22 |
+
|
| 23 |
+
- **Structure**: Feature-based packages (e.g., `navigation_stack`, `vision_pipeline`).
|
| 24 |
+
- **Rule**: Use standard interfaces. Do not depend on proprietary backend logic.
|
| 25 |
+
|
| 26 |
+
## 3. Workflow
|
| 27 |
+
|
| 28 |
+
1. **Plan**: Write `docs/planning/RFC-001-[feature].md`.
|
| 29 |
+
2. **Define Contracts**: Update `packages/shared-types`.
|
| 30 |
+
3. **Implement Robot Loop**: Add ROS node/topic.
|
| 31 |
+
4. **Implement Backend Bridge**: Update Node.js module.
|
| 32 |
+
5. **Implement Frontend UI**: Add React component.
|
| 33 |
+
6. **Report**: Write `docs/reports/2026-02-17-feature-complete.md`.
|
docs/planning/RFC-002-telemetry-streaming.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# RFC-002: Real-Time Telemetry Streaming
|
| 2 |
+
|
| 3 |
+
## 1. Problem Statement
|
| 4 |
+
|
| 5 |
+
We need a foundational pipeline to visualize the state of `N` robots in real-time on the web dashboard.
|
| 6 |
+
The latency must be low (< 50ms) and the architecture must support scaling to multiple robots without code changes.
|
| 7 |
+
|
| 8 |
+
## 2. Technical Architecture (Vertical Slice)
|
| 9 |
+
|
| 10 |
+
### A. Shared Contract (`packages/shared-types`)
|
| 11 |
+
|
| 12 |
+
We need a strict interface for the "Heartbeat" of every robot.
|
| 13 |
+
|
| 14 |
+
```typescript
|
| 15 |
+
export interface RobotState {
|
| 16 |
+
id: string; // "robot_1"
|
| 17 |
+
status: "IDLE" | "MOVING" | "ERROR";
|
| 18 |
+
battery: number; // 0-100
|
| 19 |
+
pose: {
|
| 20 |
+
x: number;
|
| 21 |
+
y: number;
|
| 22 |
+
theta: number; // Orientation in radians
|
| 23 |
+
};
|
| 24 |
+
lastUpdate: number; // Timestamp
|
| 25 |
+
}
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
### B. Robotics Layer (`robotics/ros2_ws`)
|
| 29 |
+
|
| 30 |
+
- **Package**: `simulation_manager`
|
| 31 |
+
- **Node**: `mock_robot_node`
|
| 32 |
+
- **Behavior**: Publishes standard ROS messages.
|
| 33 |
+
- `/robot_1/odom` (nav_msgs/Odometry)
|
| 34 |
+
- `/robot_1/battery` (sensor_msgs/BatteryState) -> _Simulated sine wave for now._
|
| 35 |
+
|
| 36 |
+
### C. Backend Layer (`apps/backend`)
|
| 37 |
+
|
| 38 |
+
- **Module**: `modules/telemetry`
|
| 39 |
+
- **Technology**: `rclnodejs` + `Socket.io`
|
| 40 |
+
- **Logic**:
|
| 41 |
+
1. Discover active robots (via ROS 2 discovery).
|
| 42 |
+
2. Subscribe to `/[namespace]/odom`.
|
| 43 |
+
3. Throttle data to 60Hz.
|
| 44 |
+
4. Emit to WebSocket Room: `room:robot_1`.
|
| 45 |
+
|
| 46 |
+
### D. Frontend Layer (`apps/frontend`)
|
| 47 |
+
|
| 48 |
+
- **Component**: `components/dashboard/TelemetryCard.tsx`
|
| 49 |
+
- **Hook**: `useRobotTelemetry(robotId: string)`
|
| 50 |
+
- **Behavior**: Connects to the backend websocket, joins the specific robot room, and updates local state.
|
| 51 |
+
|
| 52 |
+
## 3. Implementation Plan
|
| 53 |
+
|
| 54 |
+
1. **Phase 1 (Types)**: Define `RobotState` in shared package.
|
| 55 |
+
2. **Phase 2 (ROS)**: Create `mock_robot.py` in ROS workspace to generate fake data.
|
| 56 |
+
3. **Phase 3 (Backend)**: Set up `rclnodejs` and basic Socket.io gateway.
|
| 57 |
+
4. **Phase 4 (Frontend)**: Build the React component to display `x, y, theta`.
|
| 58 |
+
|
| 59 |
+
## 4. Risks & Mitigations
|
| 60 |
+
|
| 61 |
+
- **Risk**: High frequency Odom (100Hz) flooding the websocket.
|
| 62 |
+
- **Mitigation**: Backend **MUST** throttle/downsample to 30Hz or 60Hz before sending to UI.
|
robotics/ros2_ws/src/simulation_manager/src/mock_robot.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
|
| 3 |
+
import rclpy
|
| 4 |
+
from rclpy.node import Node
|
| 5 |
+
from nav_msgs.msg import Odometry
|
| 6 |
+
from sensor_msgs.msg import BatteryState
|
| 7 |
+
from geometry_msgs.msg import PoseWithCovarianceStamped
|
| 8 |
+
import math
|
| 9 |
+
import random
|
| 10 |
+
|
| 11 |
+
class MockRobot(Node):
|
| 12 |
+
|
| 13 |
+
def __init__(self):
|
| 14 |
+
super().__init__('mock_robot')
|
| 15 |
+
|
| 16 |
+
# Declare parameters (namespace is handled by launch file, but we can have local ID)
|
| 17 |
+
self.declare_parameter('robot_id', 'robot_1')
|
| 18 |
+
self.robot_id = self.get_parameter('robot_id').get_parameter_value().string_value
|
| 19 |
+
|
| 20 |
+
# Publishers
|
| 21 |
+
self.odom_pub = self.create_publisher(Odometry, 'odom', 10)
|
| 22 |
+
self.battery_pub = self.create_publisher(BatteryState, 'battery', 10)
|
| 23 |
+
|
| 24 |
+
# Simulation State
|
| 25 |
+
self.x = 0.0
|
| 26 |
+
self.y = 0.0
|
| 27 |
+
self.theta = 0.0
|
| 28 |
+
self.battery = 100.0
|
| 29 |
+
self.battery_drain_rate = 0.05 # % per second
|
| 30 |
+
|
| 31 |
+
# Timer (10Hz simulation loop)
|
| 32 |
+
self.create_timer(0.1, self.timer_callback)
|
| 33 |
+
|
| 34 |
+
self.get_logger().info(f'Mock Robot "{self.robot_id}" Started. Simulating movement.')
|
| 35 |
+
|
| 36 |
+
def timer_callback(self):
|
| 37 |
+
# 1. Physics Update (Circular Motion)
|
| 38 |
+
t = self.get_clock().now().nanoseconds / 1e9
|
| 39 |
+
radius = 5.0
|
| 40 |
+
speed = 0.5
|
| 41 |
+
|
| 42 |
+
self.x = radius * math.cos(speed * t)
|
| 43 |
+
self.y = radius * math.sin(speed * t)
|
| 44 |
+
|
| 45 |
+
# Calculate theta (tangent to circle)
|
| 46 |
+
self.theta = (speed * t) + (math.pi / 2)
|
| 47 |
+
|
| 48 |
+
# 2. Battery Update
|
| 49 |
+
self.battery -= (self.battery_drain_rate * 0.1)
|
| 50 |
+
if self.battery < 0:
|
| 51 |
+
self.battery = 100.0 # Charge!
|
| 52 |
+
|
| 53 |
+
# 3. Publish Odometry
|
| 54 |
+
odom_msg = Odometry()
|
| 55 |
+
odom_msg.header.stamp = self.get_clock().now().to_msg()
|
| 56 |
+
odom_msg.header.frame_id = "map"
|
| 57 |
+
odom_msg.child_frame_id = f"{self.robot_id}_base_link"
|
| 58 |
+
|
| 59 |
+
odom_msg.pose.pose.position.x = self.x
|
| 60 |
+
odom_msg.pose.pose.position.y = self.y
|
| 61 |
+
odom_msg.pose.pose.position.z = 0.0
|
| 62 |
+
|
| 63 |
+
# Simple Euler to Quaternion (Z-axis rotation only)
|
| 64 |
+
odom_msg.pose.pose.orientation.z = math.sin(self.theta / 2.0)
|
| 65 |
+
odom_msg.pose.pose.orientation.w = math.cos(self.theta / 2.0)
|
| 66 |
+
|
| 67 |
+
self.odom_pub.publish(odom_msg)
|
| 68 |
+
|
| 69 |
+
# 4. Publish Battery
|
| 70 |
+
bat_msg = BatteryState()
|
| 71 |
+
bat_msg.percentage = self.battery
|
| 72 |
+
bat_msg.voltage = 24.0
|
| 73 |
+
bat_msg.current = 2.5
|
| 74 |
+
bat_msg.charge = self.battery # Using charge field for simplicity
|
| 75 |
+
|
| 76 |
+
self.battery_pub.publish(bat_msg)
|
| 77 |
+
|
| 78 |
+
def main(args=None):
|
| 79 |
+
rclpy.init(args=args)
|
| 80 |
+
node = MockRobot()
|
| 81 |
+
try:
|
| 82 |
+
rclpy.spin(node)
|
| 83 |
+
except KeyboardInterrupt:
|
| 84 |
+
pass
|
| 85 |
+
finally:
|
| 86 |
+
node.destroy_node()
|
| 87 |
+
rclpy.shutdown()
|
| 88 |
+
|
| 89 |
+
if __name__ == '__main__':
|
| 90 |
+
main()
|
scripts/dev-all.sh
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Configuration
|
| 4 |
+
SESSION_NAME="robot_stack"
|
| 5 |
+
WSL_PROJECT_PATH="/mnt/c/Omniverse/Projects/ros2-robot-stack" # Or ~/projects/ros2-robot-stack if you moved it
|
| 6 |
+
|
| 7 |
+
# Function to kill child processes on exit
|
| 8 |
+
trap "kill 0" EXIT
|
| 9 |
+
|
| 10 |
+
echo "Launching Robotics Stack..."
|
| 11 |
+
|
| 12 |
+
# 1. Start ROS 2 Mock Robot
|
| 13 |
+
echo "[1/3] Starting ROS 2 Simulation..."
|
| 14 |
+
(
|
| 15 |
+
source /opt/ros/humble/setup.bash
|
| 16 |
+
cd $WSL_PROJECT_PATH/robotics/ros2_ws
|
| 17 |
+
# Build first to be safe
|
| 18 |
+
colcon build --packages-select simulation_manager
|
| 19 |
+
source install/setup.bash
|
| 20 |
+
# Run Node
|
| 21 |
+
python3 src/simulation_manager/src/mock_robot.py
|
| 22 |
+
) &
|
| 23 |
+
|
| 24 |
+
# 2. Start Backend
|
| 25 |
+
echo "[2/3] Starting Backend..."
|
| 26 |
+
(
|
| 27 |
+
cd $WSL_PROJECT_PATH/apps/backend
|
| 28 |
+
bun run dev
|
| 29 |
+
) &
|
| 30 |
+
|
| 31 |
+
# 3. Start Frontend
|
| 32 |
+
echo "[3/3] Starting Frontend..."
|
| 33 |
+
(
|
| 34 |
+
cd $WSL_PROJECT_PATH/apps/frontend
|
| 35 |
+
bun run dev
|
| 36 |
+
) &
|
| 37 |
+
|
| 38 |
+
# Wait for user input to exit
|
| 39 |
+
echo "✅ All systems go! Open http://localhost:3000"
|
| 40 |
+
echo "Press CTRL+C to stop everything."
|
| 41 |
+
wait
|
system-index.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"project_name": "ros2-robot-stack",
|
| 3 |
+
"environment": {
|
| 4 |
+
"host_os": "Windows",
|
| 5 |
+
"execution_runtime": "WSL 2 / Docker",
|
| 6 |
+
"notes": "Code lives on Windows FS, executes in Linux Subsystem."
|
| 7 |
+
},
|
| 8 |
+
"services": {
|
| 9 |
+
"frontend": {
|
| 10 |
+
"path": "apps/frontend",
|
| 11 |
+
"runtime": "Node (WSL)",
|
| 12 |
+
"port": 3000
|
| 13 |
+
},
|
| 14 |
+
"backend": {
|
| 15 |
+
"path": "apps/backend",
|
| 16 |
+
"runtime": "Node (WSL)",
|
| 17 |
+
"port": 4000
|
| 18 |
+
},
|
| 19 |
+
"ml-service": {
|
| 20 |
+
"path": "apps/ml-service",
|
| 21 |
+
"runtime": "Python (WSL)",
|
| 22 |
+
"port": 8000
|
| 23 |
+
},
|
| 24 |
+
"ros2_ws": {
|
| 25 |
+
"path": "robotics/ros2_ws",
|
| 26 |
+
"runtime": "ROS 2 Humble (WSL)",
|
| 27 |
+
"display": "Wayland/X11 (WSLg)"
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
}
|