Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

.nornir/viz-law4b-screenshot.md

LAW4b — Viz Self-Screenshot (ViewportCommand::Screenshot)

Design

LAW4b is the live-app half of the screenshot-proof law (LAW4):

"Screenshot PROOF, egui-native only — egui_kittest wgpu snapshot (tests) + live ViewportCommand::Screenshot (app screenshots itself) → PNG in robot_test_results. Never KDE/spectacle/compositor grabs."

The test-side half (LAW4) already shipped: egui_kittest wgpu snapshot harness (tests/viz_snapshots.rs). This feature ships the live-app half.

Architecture

Three access paths all converge on the same state machine:

CLI:  nornir viz --screenshot <path>
        └─ writes VizCommand::Screenshot to $NORNIR_VIZ_CMD
             └─ nornir-ui picks it up on frame 1

Keyboard:  Ctrl+Shift+S (inside the running viz)
             └─ calls app.request_screenshot(Some(path))

Control channel:  viz.click MCP / robot driver
                    └─ writes VizCommand::Screenshot to $NORNIR_VIZ_CMD

All three → request_screenshot(out_path) → screenshot_pending = true

Per-frame loop (in draw_ui):

  1. poll_control_channel() — consumes any VizCommand::Screenshot and calls request_screenshot(out_path).
  2. Ctrl+Shift+S keyboard check — calls request_screenshot(…) directly.
  3. If screenshot_pending == true → fire ctx.send_viewport_cmd(ViewportCommand::Screenshot(UserData::default())).
  4. Drain ctx.input(|i| i.raw.events) for Event::Screenshot { image, .. }.
  5. On receipt: encode ColorImage → PNG bytes (via the image crate, already in the viz feature graph), write to out_path on disk (when Some), and append to the warehouse robot_test_results blob store (LAW5 persist-to-warehouse).
  6. Set screenshot_pending = false, screenshot_last_path = actual_path.

State JSON shape

{
  "screenshot": {
    "pending": false,
    "last_path": "/tmp/nornir_viz_screenshot_20260614_120000.png"
  }
}

A robot-driver polls screenshot.pending == false to know the PNG is ready.

Data types

VizCommand::Screenshot
{ "screenshot": { "out_path": "/tmp/out.png" } }

out_path is optional; omit for warehouse-only mode.

ScreenshotRequest
pub struct ScreenshotRequest {
    pub out_path: Option<String>,
}

Warehouse persistence

Screenshots are appended to the robot_test_results blob store via put_blobs_dedup (content-addressed, dedup'd by sha256). The snapshot_id is screenshot-<YYYYMMDD-HHMMSS>, the filename is the basename of out_path (or screenshot.png for warehouse-only mode).

Remote/empty-local mode: the warehouse append is skipped (the blob store is local); only the disk write happens.

Guide

Take a screenshot from the CLI

nornir viz --screenshot /tmp/my_screenshot.png
# Starts the viz, takes a screenshot on frame 1, writes PNG, continues running.

Take a screenshot with the keyboard shortcut

Press Ctrl+Shift+S while the viz window is focused. The PNG is written to /tmp/nornir_viz_screenshot_<YYYYMMDD_HHMMSS>.png and appended to the warehouse.

Drive a screenshot from a robot script

Write a VizCommand::Screenshot to $NORNIR_VIZ_CMD:

# Default path: /tmp/nornir_viz_cmd.json
echo '{"screenshot":{"out_path":"/tmp/robot_shot.png"}}' > /tmp/nornir_viz_cmd.json
# The viz picks it up within 250 ms (one poll cycle), takes the screenshot,
# and writes the PNG.

Then poll $NORNIR_VIZ_STATE until screenshot.pending == false:

until python3 -c "
import json, sys
s = json.load(open('/tmp/nornir_viz_state.json'))
sys.exit(0 if not s['screenshot']['pending'] else 1)
"; do sleep 0.25; done
echo "Screenshot ready at $(python3 -c \"import json; print(json.load(open('/tmp/nornir_viz_state.json'))['screenshot']['last_path'])\")"

Test the control+state_json path headlessly

cargo test --features viz --test viz_law4b_screenshot -- --nocapture

The four tests drive apply_command and request_screenshot against a headless UrdrThreadsApp (no GPU, no window) and assert state_json.

Assertions