Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
f1b7d24
Fix two hangs on quiet robots: recv-timeout reconnect loop, state() s…
publu Jun 12, 2026
e2e42aa
One UI, two power levels: static site discovers a local runtime and g…
publu Jun 12, 2026
66466d3
Arena robot mode: a connected robot replaces the sim, same page
publu Jun 12, 2026
065dcae
Sources are first-class: per-source camera streams, LAN robot discovery
publu Jun 12, 2026
d7e0dbd
Robot mode is segmented: sim furniture out, robot interfaces in
publu Jun 12, 2026
a4b581e
Robot cockpit: a beautiful ground-control station, the camera is the …
publu Jun 13, 2026
186ed82
Cockpit, fuller: source picker, syntax-highlighted policy, live timeline
publu Jun 13, 2026
17bbe46
One view for everything: the cockpit is the only UI, sim is just a so…
publu Jun 13, 2026
124bec6
Cockpit fixes: isolate sim from robot, framed policy panel, stable si…
publu Jun 13, 2026
f4dc2ad
One view, one URL: every route serves the cockpit; retire the flight …
publu Jun 13, 2026
7e4022c
One route, not three: /deck and /arena 301-redirect to /
publu Jun 13, 2026
5134339
Cockpit UX fixes: unblock buttons, calm sim, source modal, expandable…
publu Jun 13, 2026
478f77f
Replace native confirm() with a cockpit-styled deploy modal
publu Jun 13, 2026
d52b869
Cockpit fixes: LEVELS picker opens, sim lidar map, clear HOLD toggle
publu Jun 13, 2026
ad23396
Sim cockpit: keep robot type label fresh after a LEVELS pick
publu Jun 13, 2026
8ff2275
Fix arena/state 500 + close the sim→robot policy leak
publu Jun 13, 2026
b9a715f
Don't grab the laptop webcam when a robot is the source
publu Jun 13, 2026
2aa4593
Menu is the launcher: ROBORUN ARENA front door with a ROS robot card
publu Jun 13, 2026
e0c2e81
ROS card: it's any rosbridge source, not 'real hardware'
publu Jun 13, 2026
9198b46
ROS discovery: verify rosbridge, don't list every :9090 device
publu Jun 13, 2026
4d7083f
Hide the 'enter cockpit' chip outside the sim cockpit
publu Jun 13, 2026
a40540b
Detect wheeled/diff-drive ground robots, not just legged ones
publu Jun 13, 2026
cdee8b0
Tactical map: occupancy grid like the old ROBOT MAP, bigger
publu Jun 13, 2026
a27a770
Multi-panel deck is the live-robot view, fed by telemetry
publu Jun 13, 2026
14e307d
Fleet lab: swarm coordination sandbox + teaching tool
publu Jun 15, 2026
59215ff
Always open the selector; don't trap users in the robot deck
publu Jun 15, 2026
c81d051
EYES is robot-only: never show the camera panel in the sim
publu Jun 15, 2026
14c1f93
Fleet hover: show in-memory vs lifetime (historical) knowledge
publu Jun 16, 2026
096bf45
lots of stuff
publu Jun 17, 2026
564c6dc
Build the plans: scenario harness, /scenarios UI, async anchor, recal…
publu Jun 17, 2026
169ab2b
Multi-camera source_id: additive column + migration + recall filter
publu Jun 17, 2026
0408bd4
Build the remaining specs: sqlite-vec ANN, H.264 codec video, MJX vec…
publu Jun 17, 2026
727ebfb
Wire SimBackend into the handle + streaming extraction into start_rec…
publu Jun 17, 2026
77eba02
Close the hardware-adjacent gaps: gz capability+runner, multi-camera …
publu Jun 17, 2026
6a17874
Test gz runner end-to-end against a mock gz world (detect→spawn→locks…
publu Jun 17, 2026
2f128ac
Multi-camera runtime tested end-to-end on synthetic video (real pipel…
publu Jun 17, 2026
398290e
Capstone: score a scenario across N vectorized MJX worlds (Simulate↔D…
publu Jun 17, 2026
490d236
gz on real MuJoCo physics + hardware-free synthetic camera + gz setup…
publu Jun 17, 2026
b347f97
The one system: unified PerceptionSession across sim/robot/production…
publu Jun 17, 2026
9825a59
Analytics + search/timeline UI + ROS1/2 detection + configurable data…
publu Jun 17, 2026
7e2cbad
Antioch Analyze view: per-run telemetry detail (trajectory/velocity/c…
publu Jun 17, 2026
70395f4
Fleet activity in analytics: per-robot observation counts, last-seen,…
publu Jun 17, 2026
56e6ed7
Built-in runnable scenarios + Run controls on the board
publu Jun 17, 2026
db7bc88
Semantic navigation (dimOS parity): recall_place + go_to_place + MCP …
publu Jun 17, 2026
35125ed
Time-synced playback (Roboto parity) + competitive research log
publu Jun 17, 2026
46cff75
Usage model: cockpit VIEWS nav + local-runner CLI (search / scenarios)
publu Jun 17, 2026
52b6c70
Lean local-runner UX: no mujoco_warp noise on scenario listing (lazy …
publu Jun 17, 2026
8d6cb6f
System map: the one loop, three usage modes (front-end / local-runner…
publu Jun 17, 2026
6efe536
Curate datasets from search (Foxglove parity, sealed provenance)
publu Jun 17, 2026
0be5efe
End-to-end system test + source-mode coherence
publu Jun 17, 2026
026e612
Incidents: flag a moment in a run to revisit (data-flywheel primitive)
publu Jun 17, 2026
790125b
First-run experience: 'roborun demo' seeds a populated, searchable UI
publu Jun 17, 2026
4030c6f
README: document track-and-search-over-time, the dashboards, roborun …
publu Jun 17, 2026
2865bfe
Lock Foxglove interop: messages match well-known schema shapes (runs …
publu Jun 17, 2026
ee36932
VLA adapter: robot foundation models (GR00T/OpenVLA/RT-X) as native c…
publu Jun 17, 2026
03bace7
Front-end wording: plain-language copy for normal users (search/analy…
publu Jun 17, 2026
93e9d10
Analytics copy: human KPI labels (things seen / AI-searchable / saved…
publu Jun 17, 2026
0e7b548
More front-end wording: from/to (not since/until), Result/Scores/Meas…
publu Jun 17, 2026
a966684
Clarify Fleet → 'Fleet Lab' (it's a swarm sandbox; real robot status …
publu Jun 17, 2026
e94e626
Timeline: consistent guiding empty state for the sightings strip
publu Jun 17, 2026
bdca243
README headline: surface the sealed + searchable data layer ('find an…
publu Jun 17, 2026
cd05eaf
README: replace internal 'Antioch loop' reference with plain user-fac…
publu Jun 17, 2026
2241633
Dataset provenance manifest: verifiable lineage for curated training …
publu Jun 17, 2026
7190a4e
Search first-run: a 3-step quick-start greets new users when the inde…
publu Jun 17, 2026
60b459c
Startup guidance: 'roborun' greets new users with the quick-win + whe…
publu Jun 17, 2026
992f837
Storage/retention visibility for fleet-cost awareness
publu Jun 17, 2026
55c5973
Add 'roborun help' — one screen listing all verbs (local-runner disco…
publu Jun 17, 2026
cfd0882
Fleet panel: robot status at a glance (active/idle/offline dot from l…
publu Jun 17, 2026
e358c94
Cockpit feed state: 'LIVE' → 'NO SIGNAL' when the camera feed freezes
publu Jun 17, 2026
b572ec3
CLI: 'roborun ask' (NL command to the agent) + 'roborun status' (dimO…
publu Jun 17, 2026
2b9b37e
Harden 'roborun ask': surface the agent error (no API key) instead of…
publu Jun 17, 2026
db6a42e
Mobile: scenarios header wraps + table scrolls horizontally on narrow…
publu Jun 17, 2026
bde6a6c
README quick-start: include the new CLI verbs (demo / ask / status / …
publu Jun 17, 2026
ef79620
Emergency stop: POST /api/estop + 'roborun stop' (safety primitive)
publu Jun 17, 2026
4d72967
README handle table: add robot.go_to_place (semantic navigation) so d…
publu Jun 17, 2026
edac562
Active-learning curation: 'by=uncertain' surfaces the examples worth …
publu Jun 17, 2026
f284423
Search UI: 'needs labeling' mode surfaces the active-learning set in …
publu Jun 17, 2026
9b35fb2
Fix /api/search: allow no-query for by=uncertain (active-learning cur…
publu Jun 17, 2026
79c056e
Fix the core sim/cockpit UX (the stuff that actually matters)
publu Jun 17, 2026
45192f1
Routing consistency: sim levels are /sim?level=<name>, not #hash
publu Jun 17, 2026
8f90f80
Sim gets a way out; timeline is per-run, not a global firehose
publu Jun 17, 2026
cd1ce1e
Bigger click targets + kill the dashboard flicker
publu Jun 17, 2026
6b57603
Rebuild the UI components to Antioch-grade (not a reskin)
publu Jun 17, 2026
1484aa0
Platform foundation: Project -> Environment -> Run scoping (specs 07/…
publu Jun 17, 2026
dff8a8e
Platform: search scoping (06), backend registry (03), camera reg (09)…
publu Jun 17, 2026
2fffa98
Platform: telemetry data browser (02) + persist 3D scene cloud (05)
publu Jun 17, 2026
b15b76e
Platform: fleet multi-robot data semantics (04 data side) + status
publu Jun 17, 2026
6acb8d9
Make the platform visually accessible: project scope on every page + …
publu Jun 17, 2026
0fe4f64
Fix 05 cloud persistence: flatten nested scene points for write_cloud
publu Jun 17, 2026
dceab53
06 complete: unified recall() combining clip+label+near+time + /api/r…
publu Jun 17, 2026
4228411
01 complete: GPS ingestion + channel list in run manifest
publu Jun 17, 2026
6dd5cc3
03 complete: Isaac backend driver + conformance tests
publu Jun 17, 2026
4a54567
05+09 complete: env-frame fusion + object tracks + spatial payoff view
publu Jun 17, 2026
e59b260
02 complete: composable synced viewer — one clock across every panel
publu Jun 17, 2026
29078bd
04 complete: large-world N-robot fleet sim — warehouse, floors, eleva…
publu Jun 17, 2026
3e2873a
fix stale test: isaac status is now 'available' (driver shipped)
publu Jun 17, 2026
7d059c5
Release 0.13.0 — platform: Project/Environment/Run + all 9 platform s…
publu Jun 17, 2026
77506aa
Dashboard overhaul: persistent app shell + Home + guided setup (Antio…
publu Jun 17, 2026
e9183d5
Optimize every dashboard route (11 parallel Opus agents, one per page)
publu Jun 17, 2026
b51c41b
Update UI page tests for the shell IA
publu Jun 17, 2026
b4bda47
Standardize the cockpit start modal on the dashboard design system
publu Jun 17, 2026
6c08e80
Setup is now a step-by-step wizard (one step per screen)
publu Jun 17, 2026
1a6d736
Real data from real rapier — live sim detections become searchable; u…
publu Jun 17, 2026
932839f
Real Rapier fleet: N robots in one physics warehouse → joint env data
publu Jun 17, 2026
c7b6a59
Rapier fleet: multi-floor warehouse + elevators (real physics)
publu Jun 17, 2026
9660fe7
Cockpit: always-visible ← Dashboard button; setup: drop double-number…
publu Jun 17, 2026
84e74a7
Two clean layers: Swarm Lab (coordination algorithms) vs Fleet Sim (R…
publu Jun 17, 2026
466525f
Audit: quiet the favicon 404 (204) — the only console noise on every …
publu Jun 17, 2026
eaff91f
Fix setup→cockpit double-pick: a chosen /sim?level= skips the picker
publu Jun 17, 2026
5d8ef34
Search: default to 'by object' (label), not 'by meaning' (CLIP)
publu Jun 17, 2026
720da5d
Setup: be honest that only Rapier runs in-browser; others are server-…
publu Jun 17, 2026
f4f30e1
Fleet is its own separate thing + doesn't auto-run from the start
publu Jun 17, 2026
fc04a97
Fleet Sim is now real 3D (three.js) — no more 2D-vs-3D intermingling
publu Jun 17, 2026
8bba542
Kill the white page: the shell now owns the dark background
publu Jun 17, 2026
ed313b7
Strata: object-storage-native time-series + vector engine (S3)
publu Jun 18, 2026
7d9bb78
Strata: compaction, hybrid search, atomic in-memory CAS
publu Jun 18, 2026
d25f6e5
Strata: approximate IVF ANN for turbopuffer-scale vector search
publu Jun 18, 2026
7cc80ea
Studio: unified Studio SPA (live + replay + sims + search + agent) at…
publu Jun 19, 2026
b201d54
Studio: conform to the RoboRun Design System (system mono, flat/no-gl…
publu Jun 19, 2026
96effa8
Studio: always-visible project scope chip with a real "+ New project"…
publu Jun 19, 2026
6e28bad
Repo hygiene: gitignore strategy docs + a CI guard against private leaks
publu Jun 26, 2026
0479e3e
Local-first: headless + TUI run modes, and a CORS allowlist
publu Jun 26, 2026
5e43f0a
Studio SPA: hosted <-> localhost bridge + cohesion pass, rebuilt bundle
publu Jun 26, 2026
30954b6
web/vercel.json: SPA fallback so hosted Studio deep-links work
publu Jun 26, 2026
2312af6
build_site: emit a physical sim.html so /sim works on static hosts
publu Jun 26, 2026
3f432b3
web/vercel.json: clean rewrite destinations (cleanUrls-compatible)
publu Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/no-private-leak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: no-private-leak

# Hard gate: the public repo must never track private strategy material.
# This is the backstop for the one footgun that bit us before — a `git add -f`
# slipping a private/ file past .gitignore. Runs on every push and PR.

on:
push:
pull_request:

jobs:
guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fail if any private path is tracked
run: |
set -euo pipefail
# Paths that must never be committed to the public repo.
forbidden='^(private/|site/|docs/ANTIOCH_PARITY\.md$|docs/DEMO_SPEC\.md$|docs/RL_HARNESS_SPEC\.md$)'
hits="$(git ls-files | grep -E "$forbidden" || true)"
if [ -n "$hits" ]; then
echo "::error::Private material is tracked in the public repo:"
echo "$hits"
exit 1
fi
echo "OK — no private paths tracked."
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ private/
*.pem
*.key

# Strategy / planning docs — live in the private website repo only, never public
/docs/ANTIOCH_PARITY.md
/docs/DEMO_SPEC.md
/docs/RL_HARNESS_SPEC.md

# Local agent / dev tooling — belongs in neither published repo
.agents/
.claude/
.goalkeeper/
skills-lock.json

# Logs
*.log
*.tmp
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## Unreleased

### Added
- **FLEET sandbox** (`/fleet`) — a multi-quadruped teaching arena reachable from the right of the robot picker. Configure the radio (range, link reliability), the airtime each robot gets, and its onboard memory + inbox depth, then watch a fleet split a field under four coordination strategies (lone wolves, gossip, claim-and-yield, one commander). Live links, in-flight messages, dropped packets, coverage and wasted re-walks all rendered.
- Fleet **hover-to-inspect**: mouse over any robot to highlight what *it* knows — tiles it sensed firsthand (filled) vs. only heard from peers (outlined) — with a live knowledge/inbox/data card.
- Fleet **base station + data points**: discover data and relay it home with greedy geographic routing (multi-hop / data-mule), plus selectable **environments** (open / obstacles / building with line-of-sight-blocking walls).
- Fleet **CONCEPTS panel** (expandable): in-UI explanations of delivered vs. dropped, overlapping searches, optimisation, "is this libp2p?", swarm intelligence/stigmergy, and the base-station sink — plus hover tooltips on every metric.
- Fleet **bring-your-own algorithm**: a "✨ Your algorithm" strategy with a live JS editor, and a **Generate** button that asks the local runtime's LLM (`POST /api/fleet/strategy`) to draft a coordination policy from a plain-language goal — runnable on the spot against the same radio limits.
- **`roborun/swarm/`** — the comms model, four strategies, base-station relay and data points as runnable Python (`python -m roborun.swarm`), the headless twin of the sandbox; ships with the package.
- ROS card **network scan**: an "Allow network scan to load robots" button that trips the browser's local-network permission and lists every rosbridge robot found as its own one-click view.

### Changed
- ROS-connected robots land in the **exact same multi-panel deck** as the rapier.js sim — the live camera (EYES) now docks into the deck layout where the sim shows its POV, instead of floating.
- The FLEET picker card is now a single **Open Fleet Lab** button; every knob lives inside the sandbox.
- Replaced the last native `confirm()` (deploy-to-robot) with the in-app styled modal, usable from the deck as well as the cockpit.

### Deploy
- `vercel.json` with `cleanUrls` so `/fleet` resolves on the static Vercel build, matching the local server's route.

## v0.9.2 — 2026-06-09

### Fixed
Expand Down
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<h1 align="center">RoboRun: Write a Robot Behavior Once, Run It on Any ROS 1/2 Robot</h1>

<p align="center"><b>The base layer for coding robots: <code>see / move / ask</code> primitives, hot-reload Python behaviors,<br>the same file from webcam + MuJoCo to real hardware. MCP-native for AI agents, every run flight-recorded.</b></p>
<p align="center"><b>The base layer for coding robots: <code>see / move / ask</code> primitives, hot-reload Python behaviors,<br>the same file from webcam + MuJoCo to real hardware. MCP-native for AI agents — and every run is flight-recorded, sealed, and searchable, so you can find anything your robots ever saw, across all of time.</b></p>

<p align="center">
<a href="https://pypi.org/project/ros-agent/"><img src="https://img.shields.io/pypi/v/ros-agent?style=flat-square&color=00d47e&label=pip%20install%20ros-agent" alt="PyPI"></a>
Expand All @@ -19,10 +19,19 @@

```bash
pip install ros-agent # the package keeps its PyPI name; the command is roborun
roborun
roborun # serves the UI at http://localhost:8765 (prints the URL)
```

The browser opens live, and a `behaviors/` folder appears with the robot's brain. Open **`/arena`** — a robot dog in a browser sim, body and eyes in the same world (what it does changes what it sees). Nothing else to install; the base package is three small dependencies, no torch. The robot's brain:
Open the printed URL and a `behaviors/` folder appears with the robot's brain. Open **`/arena`** — a robot dog in a browser sim, body and eyes in the same world (what it does changes what it sees). Nothing else to install; the base package is three small dependencies, no torch.

Prefer the terminal? It runs fully without the web UI:

```bash
roborun run # headless: drives behaviors, streams the see/move/ask loop to stdout
roborun tui # full-screen terminal dashboard (pip install 'ros-agent[tui]')
```

The robot's brain:

```python
# behaviors/follow_person.py (already running)
Expand Down Expand Up @@ -55,6 +64,7 @@ Want real eyes instead of the sim? `pip install 'ros-agent[vision]'` (YOLO + CLI
| `robot.delegate("fix my search pattern")` | async LLM **with tools** — it can call any MCP tool, including rewriting the running policy (hot reload applies it live) |
| `robot.tool("navigate", x=2, y=3)` | call any MCP tool from inside the policy |
| `robot.lidar()` | 360° ranges in meters, `[0]` = straight ahead |
| `robot.go_to_place("the charging dock")` | semantic navigation — recall where it last saw something, then drive there |
| `robot.remember(k, v)` / `robot.recall(k)` | memory that survives restarts |
| `robot.state` | dict that survives across loop ticks |

Expand Down Expand Up @@ -101,6 +111,33 @@ What this proves: the recorded run — images, detections, and decisions include

The UI at `http://localhost:8765` is the flight deck itself: live camera with YOLO boxes, the black box streaming, the live anchor badge, a command bar, and director keys. `M` record/seal · `V` verify · `T` tamper · `R` runs/replay · `C` sources.

## Track and search everything over time

Every run — sim, real robot, or webcam — flows through one loop: **YOLO + CLIP → sealed MCAP → a live index you can search across all of history.** So "where did I last see the forklift", "who was in the lobby yesterday", "every red mug across the fleet" are one query — semantic (CLIP), label (YOLO), place, or time window — over every run and robot.

The cockpit's **▤ VIEWS** menu opens the dashboards (also `roborun demo` to populate them instantly):

- **/search** — find anything/anyone over time; export the hits as a labeled dataset (sealed provenance).
- **/scenarios** — give a behavior a task, run it, and see if it passed; group runs into suites with a pass-rate, and re-run after every change.
- **/run** — per-run trajectory · velocity · clearance · LiDAR, with **synced playback** (scrub a moment → the frame the robot saw) and **⚑ Flag** to bookmark incidents to revisit.
- **/analytics** — detections over time, suite pass-rates, per-robot fleet activity.
- **/timeline** — the live event stream + recent sightings.

From the local runner, no browser needed:

```bash
roborun demo # load sample data so the dashboards aren't empty
roborun ask "patrol the lobby" # tell the robot what to do in plain English
roborun search "person" # across every recorded run, all-time
roborun scenarios run mjx_reach # score a scenario (vectorized MuJoCo, sealed)
roborun dataset "forklift" ./ds # curate a labeled training set from a search
roborun status # is it running, what's connected, how much recorded
```

(`roborun help` lists every verb.)

The robot handle gets it too: `robot.go_to_place("the charging dock")` navigates to where it last saw something (semantic memory), and the same `recall_place` is an MCP tool any agent can call.

## Connect a real robot

```bash
Expand All @@ -109,7 +146,7 @@ roborun connect 192.168.1.42 --move # proves it: clamped 0.5s nudge, then stop
roborun connect --scan # DDS discovery — nothing to install on the robot
```

If rosbridge isn't running on the robot yet, the command prints the exact two lines to run there — that's the whole setup. **No ROS install on your machine.** Once connected, plain `roborun` drives that robot and the same `behaviors/*.py` files now move real hardware: Unitree Go2/G1, TurtleBot, arms, drones, NVIDIA Isaac Sim, Gazebo. `robot.move()` goes to the sim if it's running, otherwise to the connected robot, always through the same safety clamps.
If rosbridge isn't running on the robot yet, the command prints the exact two lines to run there — that's the whole setup. **No ROS install on your machine, and it works with both ROS 1 and ROS 2** — rosbridge speaks both, and RoboRun detects which (the DDS path is ROS 2-only). Once connected, plain `roborun` drives that robot and the same `behaviors/*.py` files now move real hardware: Unitree Go2/G1, TurtleBot, arms, drones, NVIDIA Isaac Sim, Gazebo. `robot.move()` goes to the sim if it's running, otherwise to the connected robot, always through the same safety clamps.

Optional extras: `pip install ros-agent[vision]` (YOLO + CLIP), `[sim]` (MuJoCo), `[ros]` (direct DDS), `[crypto]` (Ed25519 signing), `[anchor]` (RFC 3161 timestamping), `[fleet]` (R2 + DuckDB cross-robot), `[all]`.

Expand Down
3 changes: 3 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
*.tsbuildinfo
14 changes: 14 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RoboRun Studio</title>
<!-- shared design tokens, served by the python server (proxied in dev) -->
<link rel="stylesheet" href="/ui.css" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
25 changes: 25 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "roborun-studio",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.0",
"uplot": "^1.6.31",
"zustand": "^5.0.2"
},
"devDependencies": {
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.7.2",
"vite": "^6.0.5"
}
}
44 changes: 44 additions & 0 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AppShell } from "./shell/AppShell";
import { useStudio } from "./store";
import { Home } from "./routes/Home";
import { Live } from "./routes/Live";
import { Runs } from "./routes/Runs";
import { Sims } from "./routes/Sims";
import { Search } from "./routes/Search";
import { Scenarios } from "./routes/Scenarios";
import { Analytics } from "./routes/Analytics";
import { Agent } from "./routes/Agent";
import { Hosted } from "./routes/Hosted";

// One throttled loop drives the playhead. 10Hz is plenty — the camera polls at
// 5Hz and events are coarse — and it avoids 60fps whole-app re-renders.
function usePlayhead() {
const tick = useStudio((s) => s.tick);
useEffect(() => {
const id = setInterval(tick, 100);
return () => clearInterval(id);
}, [tick]);
}

export function App() {
usePlayhead();
return (
<BrowserRouter basename="/studio">
<Routes>
<Route element={<AppShell />}>
<Route index element={<Home />} />
<Route path="live" element={<Live />} />
<Route path="sims" element={<Sims />} />
<Route path="runs" element={<Runs />} />
<Route path="search" element={<Search />} />
<Route path="agent" element={<Agent />} />
<Route path="swarm" element={<Hosted title="Swarm Lab" src="/fleet" />} />
<Route path="scenarios" element={<Scenarios />} />
<Route path="analytics" element={<Analytics />} />
</Route>
</Routes>
</BrowserRouter>
);
}
18 changes: 18 additions & 0 deletions app/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
// Dashboards use the system monospace stack (RoboRun DS) — no webfont.
import "./theme.css";
import { App } from "./App";
import { resolveBase, installFetchBridge } from "./runtime";

// Find the backend (same-origin local server, or a localhost roborun reached
// from the hosted site), THEN install the /api+/mcp fetch shim and mount. The
// probe is quick and fails fast when nothing's listening.
resolveBase().finally(() => {
installFetchBridge();
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
});
51 changes: 51 additions & 0 deletions app/src/panels/CameraPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState } from "react";
import { useStudio } from "../store";
import { Panel } from "./Panel";

// Source-agnostic: live shows the latest frame (polled ~5Hz via the playhead),
// replay shows the frame nearest the scrubbed t. Same component, same <img>.
export function CameraPanel() {
const { source, t } = useStudio();
const [ok, setOk] = useState(true);
// quantize to 5Hz so live polling and replay scrubbing both stay sane
const qt = Math.floor(t * 5) / 5;
const url = source.frameURL(qt);
return (
<Panel title="Camera">
<div style={{ position: "relative", background: "#000", borderRadius: "var(--r-sm)", minHeight: 160 }}>
<img
src={url}
alt="camera feed"
style={{ opacity: ok ? 1 : 0 }}
onLoad={() => setOk(true)}
onError={() => setOk(false)}
/>
{!ok && (
<div
style={{
position: "absolute",
inset: 0,
display: "grid",
placeItems: "center",
textAlign: "center",
gap: 6,
color: "var(--fg-dim)",
fontSize: 13,
}}
>
{source.kind === "live" ? (
<div>
<div style={{ fontFamily: "var(--mono)" }}>○ no camera</div>
<div style={{ marginTop: 6 }}>
Start a sim in <a href="/studio/sims" style={{ color: "var(--accent)" }}>Sims</a> or connect a robot.
</div>
</div>
) : (
<div style={{ fontFamily: "var(--mono)" }}>○ no camera frame at this point in the run</div>
)}
</div>
)}
</div>
</Panel>
);
}
29 changes: 29 additions & 0 deletions app/src/panels/DetectionsPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { useStudio } from "../store";
import { Panel } from "./Panel";

// What the robot sees at the current playhead. sceneAt(t) is source-agnostic.
export function DetectionsPanel() {
const { source, t } = useStudio();
const { detections, pose } = source.sceneAt(t);
return (
<Panel title="Detections">
{pose && (
<div style={{ fontFamily: "var(--mono)", fontSize: 12, color: "var(--fg-dim)", marginBottom: 8 }}>
pose x={pose.x.toFixed(2)} y={pose.y.toFixed(2)} z={pose.z.toFixed(2)}
</div>
)}
{detections.length ? (
detections.map((d, i) => (
<div key={i} style={{ fontFamily: "var(--mono)", fontSize: 12, lineHeight: 1.7 }}>
<span style={{ color: "var(--accent)" }}>{d.label}</span>
{d.confidence != null && <span style={{ color: "var(--fg-dim)" }}> {(d.confidence * 100).toFixed(0)}%</span>}
</div>
))
) : (
<div style={{ color: "var(--fg-dim)", fontSize: 12 }}>
{source.kind === "live" ? "No detections yet — start a sim or connect a robot." : "Nothing detected at this point in the run."}
</div>
)}
</Panel>
);
}
Loading
Loading