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

Files changed (41) hide show
  1. .agent/decisions/001-aliases-vs-scripts.md +29 -0
  2. .agent/decisions/002-ros-python-env.md +36 -0
  3. .agent/decisions/003-vertical-slicing.md +69 -0
  4. .agent/decisions/004-ui-library.md +39 -0
  5. .agent/how-to-think.md +61 -0
  6. .agent/project-memory.md +34 -0
  7. .agent/rules/how-to-think.md +155 -0
  8. .agent/rules/persona.md +27 -0
  9. .agent/rules/system-manifesto.md +135 -0
  10. .agent/workflows/build.md +55 -0
  11. .agent/workflows/deploy.md +26 -0
  12. .agent/workflows/dev-start.md +50 -0
  13. .agent/workflows/lint.md +27 -0
  14. .agent/workflows/ros2-launch.md +24 -0
  15. .agent/workflows/test.md +41 -0
  16. .agent/workflows/type-check.md +20 -0
  17. .gitignore +0 -6
  18. README.md +27 -26
  19. apps/backend/bun.lock +188 -0
  20. apps/backend/package.json +11 -0
  21. apps/backend/src/index.ts +31 -0
  22. apps/backend/src/modules/telemetry/telemetry.gateway.ts +105 -0
  23. apps/frontend/app/globals.css +199 -15
  24. apps/frontend/app/page.tsx +121 -57
  25. apps/frontend/bun.lock +56 -0
  26. apps/frontend/components.json +23 -0
  27. apps/frontend/components/layout/DashboardLayout.tsx +21 -0
  28. apps/frontend/components/layout/Sidebar.tsx +92 -0
  29. apps/frontend/components/ui/avatar.tsx +50 -0
  30. apps/frontend/components/ui/badge.tsx +36 -0
  31. apps/frontend/components/ui/button.tsx +57 -0
  32. apps/frontend/components/ui/card.tsx +76 -0
  33. apps/frontend/components/ui/table.tsx +120 -0
  34. apps/frontend/hooks/useFleetTelemetry.ts +65 -0
  35. apps/frontend/package.json +10 -1
  36. apps/frontend/tailwind.config.js +19 -0
  37. docs/planning/RFC-001-vertical-architecture.md +33 -0
  38. docs/planning/RFC-002-telemetry-streaming.md +62 -0
  39. robotics/ros2_ws/src/simulation_manager/src/mock_robot.py +90 -0
  40. scripts/dev-all.sh +41 -0
  41. 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
- A production-grade fleet management system with strict separation of concerns, simulating a real-world robotics deployment environment.
4
 
5
- ## 🚀 Quick Start (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. Prerequisities
10
 
11
  - **WSL 2** (Ubuntu 22.04 recommended)
12
- - **Bun** (`curl -fsSL https://bun.sh/install | bash`)
13
- - **UV** (Python Manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`)
14
  - **ROS 2 Humble** (Installed in WSL)
15
  - **Docker Desktop** (With WSL 2 backend enabled)
16
 
17
- ### 2. How to Access WSL 2
18
 
19
- If you are on Windows:
20
 
21
  1. Open PowerShell.
22
  2. Type `wsl` and hit Enter.
23
- 3. You are now in Linux. Navigate to your project (best if cloned inside `~`):
24
  ```bash
25
  cd ~/projects/ros2-robot-stack
26
  ```
27
 
28
- ### 3. Initialize & Build
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
- # Install Python deps 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
- # Return to root
43
  cd ../..
44
-
45
- # Build Shared Types & Apps
46
  bun run build:all
47
  ```
48
 
49
- ### 4. Running the Stack
50
 
51
- Run each service in a separate terminal tab (inside WSL).
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
- ## 📂 Architecture
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
- ## 🔗 Connectivity Model
103
 
104
  All services run inside the **Same Network Namespace** (localhost) when using WSL 2.
105
 
106
- - **Frontend (3000)** -> HTTP/WS -> **Backend (4000)**
107
- - **Backend** -> TCP/DDS -> **ROS 2 Nodes**
108
- - **ML Service (8000)** -> DDS (Topics) -> **ROS 2 Nodes**
 
 
 
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
- @import "tailwindcss";
2
 
3
- :root {
4
- --background: #ffffff;
5
- --foreground: #171717;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  }
7
 
8
  @theme inline {
 
 
 
 
 
 
 
9
  --color-background: var(--background);
10
  --color-foreground: var(--foreground);
11
- --font-sans: var(--font-geist-sans);
12
- --font-mono: var(--font-geist-mono);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  }
14
 
15
- @media (prefers-color-scheme: dark) {
16
- :root {
17
- --background: #0a0a0a;
18
- --foreground: #ededed;
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  }
21
 
22
- body {
23
- background: var(--background);
24
- color: var(--foreground);
25
- font-family: Arial, Helvetica, sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- import Image from "next/image";
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  export default function Home() {
 
 
4
  return (
5
- <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
- <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
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
- <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{" "}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{" "}
27
- or the{" "}
28
- <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
31
- >
32
- Learning
33
- </a>{" "}
34
- center.
35
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  </div>
37
- <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
- <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
41
- target="_blank"
42
- rel="noopener noreferrer"
43
- >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
50
- />
51
- Deploy Now
52
- </a>
53
- <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/[.08] px-5 transition-colors hover:border-transparent hover:bg-black/[.04] dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
56
- target="_blank"
57
- rel="noopener noreferrer"
58
- >
59
- Documentation
60
- </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  </div>
62
- </main>
63
- </div>
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
+ }