一键导入
ribir-debug
// Specialized guide for debugging Ribir applications using the HTTP Debug Tool API. Use this skill when asked to debug, inspect, or interact with running Ribir applications.
// Specialized guide for debugging Ribir applications using the HTTP Debug Tool API. Use this skill when asked to debug, inspect, or interact with running Ribir applications.
Specialized guide for creating new Ribir UI widgets. Use this skill when asked to create, implement, or refactor a widget in the Ribir framework, especially when the work should include a gallery showcase or a hands-on UI walkthrough.
Specialized for code style and cleanliness within the Ribir UI framework. Use when working with Ribir DSL (@, rdl!, pipe!), state management ($read, $write, part_writer), or performance optimizations.
| name | ribir-debug |
| description | Specialized guide for debugging Ribir applications using the HTTP Debug Tool API. Use this skill when asked to debug, inspect, or interact with running Ribir applications. |
This guide explains how to use the Ribir Debug Tool HTTP API to debug, inspect, and interact with running Ribir applications.
Ribir provides a built-in HTTP debug server that enables:
App (Native/WASM) --WebSocket--> CLI Debug Server --HTTP--> Debug Tools/UI
Both Native and WASM targets use the same unified architecture. The app acts as a WebSocket client connecting to the CLI debug server.
Before running any app with debug feature, start the debug server:
cargo run -p ribir-cli -- debug-server
The debug server prints its URL on startup:
RIBIR_DEBUG_URL=http://127.0.0.1:2333
Important: The actual port may differ if the default port is in use. Always use the printed RIBIR_DEBUG_URL value.
Run your Ribir application with the debug feature. The app will automatically connect to the debug server:
cargo run --features debug
# Or with specific package:
cargo run -p your_package --features debug
To use a custom debug server URL:
RIBIR_DEBUG_URL=http://127.0.0.1:8080 cargo run -p your_package --features debug
Use ribir-cli run-wasm with the --debug flag:
cargo run -p ribir-cli -- run-wasm --package your_package --debug
The debug server URL is automatically injected into the HTML as a query parameter:
http://127.0.0.1:8000/?ribir_debug_server=http://127.0.0.1:2333
Open the browser and the WASM app will automatically connect to the debug server.
Debug Server:
Important: Always use the printed RIBIR_DEBUG_URL rather than assuming a specific port.
The debug server can be configured via CLI arguments:
| Argument | Description | Default |
|---|---|---|
--host | Host to bind | 127.0.0.1 |
--port | Port to bind | 2333 |
Base URL: http://127.0.0.1:<port> (use the printed RIBIR_DEBUG_URL)
| Endpoint | Method | Description |
|---|---|---|
/ | GET | Debug UI page (web interface) |
/ui | GET | Debug UI page (web interface) |
/status | GET | Server status, log filter, recording state |
/windows | GET | List all active windows |
Example: Get Status
curl http://127.0.0.1:2333/status
Response:
{
"recording": false,
"log_sink_connected": true,
"filter_reload_installed": true,
"filter": "info",
"dropped_total": 0,
"ring_len": 42,
"capture_root": "captures",
"active_capture": null,
"active_macro_recording": null
}
Example: Get Windows
curl http://127.0.0.1:2333/windows
Response:
[
{
"id": 140375628188560,
"title": "My App",
"width": 800,
"height": 600
}
]
Note: Use the id from /windows as window_id in other endpoints (like /events/inject, /overlay, etc.) for multi-window apps.
| Endpoint | Method | Description |
|---|---|---|
/inspect/tree | GET | Full widget tree with layout info |
/inspect/{id} | GET | Details for a specific widget |
Query Parameters:
options: Comma-separated tokens: all, id, layout, global_pos, clamp, props, no_global_pos, no_clamp, no_propsExample: Inspect Widget Tree
curl "http://127.0.0.1:2333/inspect/tree?options=all"
| Endpoint | Method | Description |
|---|---|---|
/screenshot | GET | PNG screenshot of the active window |
Example:
curl -o screenshot.png http://127.0.0.1:2333/screenshot
| Endpoint | Method | Description |
|---|---|---|
/overlay | POST | Add visual overlay to a widget |
/overlays | GET | List all active overlays |
/overlays | DELETE | Clear all overlays |
/overlay/{id} | DELETE | Remove specific overlay |
Request Body for /overlay:
{
"window_id": null,
"id": "3:0",
"color": "#FF000080"
}
Note: window_id is optional. If omitted, the first active window is used. For multi-window apps, query /windows first to get the correct window ID.
Example: Add Overlay
curl -X POST http://127.0.0.1:2333/overlay \
-H "Content-Type: application/json" \
-d '{"id": "3:0", "color": "#FF000080"}'
| Endpoint | Method | Description |
|---|---|---|
/events/inject | POST | Inject UI events |
Request Body:
{
"window_id": null,
"events": [
{ "type": "click", "id": "3:0" }
]
}
Note: window_id is optional (uses first window if omitted). The accepted field indicates how many events were actually processed (may be less than sent if some fail).
Response:
{"accepted": 1}
| Endpoint | Method | Description |
|---|---|---|
/logs | GET | Recent logs (NDJSON) |
/logs/stream | GET | Real-time log stream (SSE) |
/logs/filter | POST | Set log filter |
Query Parameters for /logs:
since_ts: Unix timestamp in millisecondsuntil_ts: Unix timestamp in millisecondslimit: Maximum number of log linesResponse Headers for /logs:
X-Ribir-Log-Dropped: Number of logs dropped due to ring buffer overflow (indicates data loss)Example: Get Logs
curl http://127.0.0.1:2333/logs?limit=50
Example: Set Filter
curl -X POST http://127.0.0.1:2333/logs/filter \
-H "Content-Type: application/json" \
-d '{"filter": "debug,ribir_core=trace"}'
| Endpoint | Method | Description |
|---|---|---|
/recording | POST | Toggle frame recording |
/capture/start | POST | Start capture session |
/capture/stop | POST | Stop capture session |
/capture/one_shot | POST | One-click capture |
| Endpoint | Method | Description |
|---|---|---|
/events/macro/start | POST | Start recording user interaction events |
/events/macro/stop | POST | Stop recording and return the macro |
Start Recording (Async Mode):
# Async mode: starts recording, returns immediately with recording_id
curl -X POST http://127.0.0.1:2333/events/macro/start \
-H "Content-Type: application/json" \
-d '{}'
Response (Async Mode):
{
"recording_id": "macro_1234567890_0",
"started_at_ts_unix_ms": 1234567890123
}
Start with Auto-Stop (Sync Mode):
# Sync mode: waits for duration_ms, then returns recorded events
curl -X POST http://127.0.0.1:2333/events/macro/start \
-H "Content-Type: application/json" \
-d '{"duration_ms": 10000}'
Response (Sync Mode - waits for recording to complete):
{
"recording_id": "macro_1234567890_0",
"events": [
{"type": "click", "id": "3:0"},
{"type": "delay", "ms": 100},
{"type": "chars", "chars": "hello"}
],
"duration_ms": 10000
}
Key Difference:
duration_ms): Returns immediately with recording_id and started_at_ts_unix_ms. You must call /events/macro/stop later.duration_ms): Waits for the specified duration, then returns the complete recording with events and duration_ms. No need to call stop.Stop Recording (Async Mode Only):
curl -X POST http://127.0.0.1:2333/events/macro/stop \
-H "Content-Type: application/json" \
-d '{}'
Response:
{
"recording_id": "macro_1234567890_0",
"events": [
{"type": "click", "id": "3:0"},
{"type": "delay", "ms": 100},
{"type": "chars", "chars": "hello"}
],
"duration_ms": 200
}
The events array contains replay-ready events with automatic delay insertions to preserve the original timing between user interactions.
duration_ms reports the full wall-clock recording duration. For timed auto-stop, it should match the configured duration (plus small scheduling jitter), not the timestamp of the last recorded event.
Replay: The events array in the response is replay-ready and contains delay events to preserve the original timing. You can pass it directly to /events/inject:
# Step 1: Record and stop
RESPONSE=$(curl -s -X POST http://127.0.0.1:2333/events/macro/stop \
-H "Content-Type: application/json" -d '{}')
# Step 2: Extract events and replay directly (includes timing delays)
EVENTS=$(echo "$RESPONSE" | jq '.events')
curl -X POST http://127.0.0.1:2333/events/inject \
-H "Content-Type: application/json" \
-d "{\"events\": $EVENTS}"
Or with the debug UI: click Start → interact with the app → click Stop → the events will be displayed with timing information. Click the Download Events button to save them as JSON. The downloaded JSON can be passed directly to /events/inject for replay with preserved timing.
Use Cases:
Widget identifiers support multiple formats:
| Format | Example | Description |
|---|---|---|
| Numeric | "3" | Matched by index1 |
| Colon shorthand | "3:0" | index1:stamp |
| Full JSON | '{"index1":3,"stamp":0}' | Full WidgetId |
| Debug name | "name:counter_button" | By debug_name |
Assign stable names to widgets for easier targeting:
button! {
debug_name: "counter_button",
@{ "+1" }
}
Then use in API calls:
curl -X POST http://127.0.0.1:2333/events/inject \
-H "Content-Type: application/json" \
-d '{"events": [{"type": "click", "id": "name:counter_button"}]}'
// Move cursor
{ "type": "cursor_moved", "x": 100, "y": 200 }
// Cursor leaves window
{ "type": "cursor_left" }
// Mouse button (button: primary|secondary|auxiliary|fourth|fifth)
{ "type": "mouse_input", "button": "primary", "state": "pressed" }
{ "type": "mouse_input", "button": "primary", "state": "released" }
// Scroll
{ "type": "mouse_wheel", "delta_x": 0, "delta_y": -10 }
// Click shortcut (auto-generates press + release)
{ "type": "click", "id": "3:0" }
{ "type": "click", "x": 100, "y": 200 }
// Double click
{ "type": "double_click", "id": "name:my_button" }
// Functional keyboard input (press + release + optional chars)
{ "type": "keyboard_input", "key": "a", "chars": "a" }
{ "type": "keyboard_input", "key": "Enter" }
// Raw keyboard input (low-level control)
{ "type": "raw_keyboard_input", "key": "a", "physical_key": "KeyA", "state": "pressed" }
{ "type": "raw_keyboard_input", "key": "a", "physical_key": "KeyA", "state": "released" }
// Quick text input
{ "type": "chars", "chars": "hello world" }
// Modifiers
{ "type": "modifiers_changed", "shift": true, "ctrl": false, "alt": false, "logo": false }
Raw Keyboard Input Fields:
key: Virtual key name (e.g., "a", "Enter", "Space")physical_key: Optional W3C physical key code (e.g., "KeyA", "Digit1", "Enter")state: "pressed" or "released"is_repeat: Optional, whether this is a key repeat event (default: false)location: Optional key location: "standard", "left", "right", "numpad" (default: "standard")chars: Optional characters to receive on press// Delay between events
{ "type": "delay", "ms": 100 }
// Request redraw
{ "type": "redraw_request", "force": true }
# Get the widget tree
curl http://127.0.0.1:2333/inspect/tree | jq .
# Find a specific widget
curl http://127.0.0.1:2333/inspect/3:0 | jq .
# Highlight a widget
curl -X POST http://127.0.0.1:2333/overlay \
-H "Content-Type: application/json" \
-d '{"id": "3:0", "color": "#FF000080"}'
# Take a screenshot to see the overlay
curl -o debug.png http://127.0.0.1:2333/screenshot
# Clear overlays when done
curl -X DELETE http://127.0.0.1:2333/overlays
# Click a button by debug name
curl -X POST http://127.0.0.1:2333/events/inject \
-H "Content-Type: application/json" \
-d '{"events": [{"type": "click", "id": "name:submit_button"}]}'
# Type text into an input field
curl -X POST http://127.0.0.1:2333/events/inject \
-H "Content-Type: application/json" \
-d '{"events": [{"type": "chars", "chars": "Hello, World!"}]}'
# Complex interaction sequence
curl -X POST http://127.0.0.1:2333/events/inject \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "type": "cursor_moved", "x": 100, "y": 100 },
{ "type": "delay", "ms": 100 },
{ "type": "mouse_input", "button": "primary", "state": "pressed" },
{ "type": "delay", "ms": 50 },
{ "type": "mouse_input", "button": "primary", "state": "released" }
]
}'
# Increase log verbosity
curl -X POST http://127.0.0.1:2333/logs/filter \
-H "Content-Type: application/json" \
-d '{"filter": "debug,ribir_core=trace"}'
# Stream logs in real-time
curl -N http://127.0.0.1:2333/logs/stream
# One-shot capture (logs + frames)
curl -X POST http://127.0.0.1:2333/capture/one_shot \
-H "Content-Type: application/json" \
-d '{"include": ["logs", "images"], "pre_ms": 2000, "post_ms": 1000}'
# One-shot capture with settle time (wait for frame to stabilize)
curl -X POST http://127.0.0.1:2333/capture/one_shot \
-H "Content-Type: application/json" \
-d '{"include": ["logs", "images"], "pre_ms": 2000, "post_ms": 1000, "settle_ms": 150}'
# Response contains the capture directory
# {"capture_dir": "/path/to/captures/{capture_id}", "manifest_path": "..."}
Capture One-Shot Parameters:
include: Array of "logs" and/or "images"pre_ms: Logs captured from (now - pre_ms) (default: 2000)post_ms: Logs captured until (now + post_ms) (default: 1000)settle_ms: Extra time to wait after frame update (default: 150)output_dir: Optional custom output directoryWhen debugging, add debug_name to target widgets:
@Button {
debug_name: "login_button",
on_tap: move |_| { /* ... */ },
@{ "Login" }
}
This makes event injection much more reliable:
{ "type": "click", "id": "name:login_button" }
Before injecting events, verify the widget exists:
# Check if widget exists
curl http://127.0.0.1:2333/inspect/name:my_button
Before clicking, add an overlay to confirm the target:
curl -X POST http://127.0.0.1:2333/overlay \
-H "Content-Type: application/json" \
-d '{"id": "name:my_button", "color": "#00FF0080"}'
# Then capture screenshot to verify
curl -o check.png http://127.0.0.1:2333/screenshot
Always verify the debug server is responsive:
curl http://127.0.0.1:2333/status
If ring_len is 0 in status:
log_sink_connected is true/inspect/{id}If screenshots timeout:
{"type": "redraw_request", "force": true}Widget IDs can change between frames if the widget tree is rebuilt. Use debug_name for stable identifiers, or re-query the widget tree before each operation.
If the debug server fails to start:
cargo run -p ribir-cli -- debug-server --port 3000RIBIR_DEBUG_URL for the actual portcargo run -p ribir-cli -- debug-server)ribir_debug_server query parameter is present in the URLRIBIR_DEBUG_URL