Rebuild grid agent TUI with Ratatui dashboards
Some checks failed
CI / rust-skia (Rust only) (push) Has been cancelled
CI / required (push) Has been cancelled

This commit is contained in:
2026-08-23 18:40:27 +02:00
parent 5a533dfa00
commit 95cca3e777
7 changed files with 2283 additions and 435 deletions

View File

@@ -22,6 +22,7 @@ metacrate-game-loop = { version = "0.0.1", path = "../metacrate-game-loop" }
metacrate-rendering-wgpu = { version = "0.0.1", path = "../metacrate-rendering-wgpu" }
mentra = { version = "0.18.3", default-features = false }
jpeg-encoder = "0.6.1"
ratatui = { version = "0.30.2", default-features = false, features = ["crossterm_0_29"] }
rustls = { version = "0.23.43", default-features = false, features = ["aws_lc_rs", "std", "tls12"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

File diff suppressed because it is too large Load Diff

View File

@@ -75,7 +75,7 @@ impl TuiTransport for FakeTransport {
}
ControlRequest::ListObservabilityEvents { .. } => {
ControlPayload::ObservabilityEvents(crate::Page {
items: Vec::new(),
items: vec![structured_event()],
next_cursor: None,
})
}
@@ -91,6 +91,31 @@ impl TuiTransport for FakeTransport {
}
}
fn structured_event() -> crate::StructuredEvent {
serde_json::from_value(serde_json::json!({
"schema_version": 1,
"event_id": 7,
"unix_millis": 1234,
"severity": "warning",
"component": "control_queue",
"family": "queue_pressure",
"correlation": {
"avatar_id": "avatar-7",
"session_id": "session-7",
"request_id": null,
"action_id": "action-7"
},
"origin": "service",
"duration_millis": 19,
"retry_count": 1,
"result_code": "delayed",
"reason_code": "capacity",
"redaction_flags": [],
"fields": {}
}))
.expect("event fixture")
}
fn empty_metrics() -> crate::MetricsSnapshot {
serde_json::from_value(serde_json::json!({
"ready":true,"reconnects":0,"active_sessions":0,"active_tasks":0,
@@ -116,6 +141,96 @@ async fn snapshot_refresh_is_transport_neutral_and_bounded() {
"running"
);
assert_eq!(transport.0.lock().expect("requests").len(), 8);
app.refresh(&transport).await.expect("stable refresh");
assert_eq!(
app.snapshot.timeline.len(),
1,
"refresh must deduplicate events"
);
}
#[tokio::test]
async fn performance_history_is_bounded_and_large_dashboards_are_deterministic() {
let transport = FakeTransport::new();
let mut app = OperatorTui::default();
for _ in 0..140 {
app.refresh(&transport).await.expect("refresh");
}
assert_eq!(app.performance_history_len(), 120);
app.screen = OperatorScreen::Overview;
let unicode = app.render(TuiRenderOptions {
width: 140,
height: 42,
color: false,
});
assert!(unicode.contains("Café") && unicode.contains('世') && unicode.contains('界'));
for (screen, marker) in [
(OperatorScreen::Overview, "Runtime"),
(OperatorScreen::Sessions, "Sessions"),
(OperatorScreen::QueuesAndBudgets, "Work queue"),
(OperatorScreen::Roaming, "Roaming schedules"),
(OperatorScreen::Approvals, "Pending approvals"),
(OperatorScreen::Timeline, "Timeline / audit"),
(OperatorScreen::Health, "Health"),
(OperatorScreen::Errors, "Recent errors"),
(OperatorScreen::Diagnostics, "Diagnostics (redacted)"),
(OperatorScreen::Preferences, "Preferences"),
] {
app.screen = screen;
let first = app.render(TuiRenderOptions {
width: 140,
height: 42,
color: true,
});
let second = app.render(TuiRenderOptions {
width: 140,
height: 42,
color: true,
});
assert!(first.contains(marker), "missing {marker}");
assert_eq!(first.lines().count(), second.lines().count());
}
app.screen = OperatorScreen::QueuesAndBudgets;
let output = app.render(TuiRenderOptions {
width: 140,
height: 42,
color: false,
});
for label in [
"Work queue",
"Rate limit",
"Inference avg ms",
"Tool avg ms",
"Dropped total",
] {
assert!(output.contains(label), "missing {label}");
}
}
#[test]
fn event_search_covers_correlation_duration_result_and_reason() {
let mut app = OperatorTui::default();
app.snapshot.timeline.push_back(structured_event());
app.screen = OperatorScreen::Timeline;
for query in [
"warning",
"avatar-7",
"session-7",
"action-7",
"19",
"delayed",
"capacity",
] {
app.filter.query = query.into();
assert!(
app.render(TuiRenderOptions {
width: 120,
height: 30,
color: false
})
.contains("queue_pressure")
);
}
}
#[test]
@@ -142,7 +257,7 @@ fn every_screen_renders_without_a_terminal_at_small_unicode_and_mono_sizes() {
color: false,
});
assert!(!output.is_empty());
assert!(output.lines().count() <= 4);
assert!(output.lines().count() <= 5);
assert!(
output
.lines()
@@ -196,6 +311,21 @@ fn every_global_command_has_a_keyboard_path() {
assert_eq!(app.command_shortcut('s'), Some(OperatorCommand::Shutdown));
}
#[test]
fn confirmation_dialog_is_visible_without_color() {
let mut app = OperatorTui::default();
assert_eq!(
app.reduce(TuiInput::Command(OperatorCommand::Shutdown)),
TuiAction::None
);
let output = app.render(TuiRenderOptions {
width: 100,
height: 24,
color: false,
});
assert!(output.contains("confirm HighRisk (y/n)"));
}
#[test]
fn transport_errors_are_safe_display_text() {
struct Broken;

View File

@@ -2,7 +2,7 @@ use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
const ALLOWED_DEPENDENCIES: [&str; 22] = [
const ALLOWED_DEPENDENCIES: [&str; 23] = [
"async-trait",
"base64",
"crossterm",
@@ -16,6 +16,7 @@ const ALLOWED_DEPENDENCIES: [&str; 22] = [
"mentra",
"jpeg-encoder",
"libremetaverse-types",
"ratatui",
"rustls",
"serde",
"serde_json",

View File

@@ -108,3 +108,23 @@ fn dotenv_import_is_explicit_and_preferences_panel_never_renders_secrets() {
let _ = tui.reduce(TuiInput::PreferenceSave);
fs::remove_dir_all(directory).unwrap();
}
#[test]
fn preferences_form_keeps_validation_feedback_visible() {
let directory = temporary("validation");
let config = directory.join("config.yml");
let mut tui = OperatorTui::with_preferences(config).unwrap();
tui.screen = OperatorScreen::Preferences;
let _ = tui.reduce(TuiInput::PreferenceEditOrCommit);
for value in "not-a-mode".chars() {
let _ = tui.reduce(TuiInput::PreferenceCharacter(value));
}
let _ = tui.reduce(TuiInput::PreferenceEditOrCommit);
let rendered = tui.render(TuiRenderOptions {
width: 100,
height: 24,
color: false,
});
assert!(rendered.contains("[VALIDATION] mode must be offline, integrated, or split"));
fs::remove_dir_all(directory).unwrap();
}