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

271
Cargo.lock generated
View File

@@ -1867,6 +1867,12 @@ version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "by_address"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.25.2" version = "1.25.2"
@@ -1931,6 +1937,15 @@ dependencies = [
"wayland-client", "wayland-client",
] ]
[[package]]
name = "castaway"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.2" version = "1.4.2"
@@ -2151,6 +2166,20 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "compact_str"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"rustversion",
"ryu",
"static_assertions",
]
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.5.0" version = "2.5.0"
@@ -2539,6 +2568,40 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "darling"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 3.0.3",
]
[[package]]
name = "darling_macro"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [
"darling_core",
"quote",
"syn 3.0.3",
]
[[package]] [[package]]
name = "dasp_sample" name = "dasp_sample"
version = "0.11.0" version = "0.11.0"
@@ -3495,6 +3558,8 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [ dependencies = [
"allocator-api2",
"equivalent",
"foldhash 0.2.0", "foldhash 0.2.0",
] ]
@@ -3861,6 +3926,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a2c462a4d927d512f5f882a033ddd62f33a05bb9f230d98f736ac3dc85938f" checksum = "e4a2c462a4d927d512f5f882a033ddd62f33a05bb9f230d98f736ac3dc85938f"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "1.1.0" version = "1.1.0"
@@ -3917,6 +3988,15 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "indoc"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
dependencies = [
"rustversion",
]
[[package]] [[package]]
name = "inflections" name = "inflections"
version = "1.1.1" version = "1.1.1"
@@ -3932,6 +4012,19 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "instability"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf84e73fa6f27f299dec58e13223cf70db80da872eb921d4f6138342a0eabc8"
dependencies = [
"darling",
"indoc",
"proc-macro2",
"quote",
"syn 3.0.3",
]
[[package]] [[package]]
name = "inventory" name = "inventory"
version = "0.3.24" version = "0.3.24"
@@ -4139,6 +4232,17 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "kasuari"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
dependencies = [
"hashbrown 0.16.1",
"portable-atomic",
"thiserror 2.0.20",
]
[[package]] [[package]]
name = "ktx2" name = "ktx2"
version = "0.5.0" version = "0.5.0"
@@ -4411,6 +4515,15 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "line-clipping"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e752191d037c44ad111a8caa762921926658402f01cc1253f7bef2020ece4f5e"
dependencies = [
"bitflags 2.13.1",
]
[[package]] [[package]]
name = "linebender_resource_handle" name = "linebender_resource_handle"
version = "0.1.1" version = "0.1.1"
@@ -4456,6 +4569,15 @@ version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lru"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
dependencies = [
"hashbrown 0.17.1",
]
[[package]] [[package]]
name = "lru-slab" name = "lru-slab"
version = "0.1.2" version = "0.1.2"
@@ -4544,7 +4666,7 @@ dependencies = [
"serde_json", "serde_json",
"serde_yaml_ng", "serde_yaml_ng",
"similar", "similar",
"strum", "strum 0.27.2",
"thiserror 2.0.20", "thiserror 2.0.20",
"time", "time",
"tokio", "tokio",
@@ -4564,7 +4686,7 @@ dependencies = [
"reqwest 0.12.28", "reqwest 0.12.28",
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum 0.27.2",
"thiserror 2.0.20", "thiserror 2.0.20",
"time", "time",
"tokio", "tokio",
@@ -4631,6 +4753,7 @@ dependencies = [
"metacrate-game-loop", "metacrate-game-loop",
"metacrate-lsl-tools", "metacrate-lsl-tools",
"metacrate-rendering-wgpu", "metacrate-rendering-wgpu",
"ratatui",
"rustls", "rustls",
"serde", "serde",
"serde_json", "serde_json",
@@ -4942,6 +5065,15 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "objc-sys" name = "objc-sys"
version = "0.3.5" version = "0.3.5"
@@ -5509,6 +5641,39 @@ dependencies = [
"sha2 0.10.9", "sha2 0.10.9",
] ]
[[package]]
name = "palette"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddeed8580d347d2abf3dcf06a5f0b3dc020258338526b277847cd4248a70fc64"
dependencies = [
"approx",
"libm",
"palette_derive",
"palette_math",
]
[[package]]
name = "palette_derive"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88537020289b719d81be994ccf1bbf4990f477e2f69ee52fe3e45f43a02e56be"
dependencies = [
"by_address",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "palette_math"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e6eb142958d64335fb0e345c5b9ead2ecd6fc438c307e9d7d3c4fd428dbaf12"
dependencies = [
"libm",
]
[[package]] [[package]]
name = "parking" name = "parking"
version = "2.2.1" version = "2.2.1"
@@ -6005,6 +6170,72 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08"
[[package]]
name = "ratatui"
version = "0.30.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3274ba0a2c5e1bcad2a2005d20f4dc59dad26b2eb0940fb094500dba4099d57d"
dependencies = [
"instability",
"ratatui-core",
"ratatui-crossterm",
"ratatui-widgets",
"serde",
]
[[package]]
name = "ratatui-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbb175c433c8e28a809d1f5773a2ae96e68c0ce40db865cbab1020bf33ae479c"
dependencies = [
"bitflags 2.13.1",
"compact_str",
"hashbrown 0.17.1",
"itertools 0.14.0",
"kasuari",
"lru",
"palette",
"serde",
"strum 0.28.0",
"thiserror 2.0.20",
"unicode-segmentation",
"unicode-truncate",
"unicode-width 0.2.2",
]
[[package]]
name = "ratatui-crossterm"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567584a3b0e6a8203c23de40b4861497266725eb5363dbfd18a1edd603cca9f0"
dependencies = [
"cfg-if",
"crossterm",
"instability",
"ratatui-core",
]
[[package]]
name = "ratatui-widgets"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e3d19bcc9130ca376277d93b60767ff121ace3be06f5f95f81dd68956407d1"
dependencies = [
"bitflags 2.13.1",
"hashbrown 0.17.1",
"indoc",
"instability",
"itertools 0.14.0",
"line-clipping",
"ratatui-core",
"serde",
"strum 0.28.0",
"time",
"unicode-segmentation",
"unicode-width 0.2.2",
]
[[package]] [[package]]
name = "raw-window-handle" name = "raw-window-handle"
version = "0.6.2" version = "0.6.2"
@@ -7149,7 +7380,16 @@ version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
dependencies = [ dependencies = [
"strum_macros", "strum_macros 0.27.2",
]
[[package]]
name = "strum"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
dependencies = [
"strum_macros 0.28.0",
] ]
[[package]] [[package]]
@@ -7164,6 +7404,18 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "strum_macros"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"
@@ -7314,7 +7566,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
dependencies = [ dependencies = [
"deranged", "deranged",
"libc",
"num-conv", "num-conv",
"num_threads",
"powerfmt", "powerfmt",
"serde_core", "serde_core",
"time-core", "time-core",
@@ -7693,6 +7947,17 @@ version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "unicode-truncate"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
dependencies = [
"itertools 0.14.0",
"unicode-segmentation",
"unicode-width 0.2.2",
]
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.14" version = "0.1.14"

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" } metacrate-rendering-wgpu = { version = "0.0.1", path = "../metacrate-rendering-wgpu" }
mentra = { version = "0.18.3", default-features = false } mentra = { version = "0.18.3", default-features = false }
jpeg-encoder = "0.6.1" 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"] } rustls = { version = "0.23.43", default-features = false, features = ["aws_lc_rs", "std", "tls12"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1" 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 { .. } => { ControlRequest::ListObservabilityEvents { .. } => {
ControlPayload::ObservabilityEvents(crate::Page { ControlPayload::ObservabilityEvents(crate::Page {
items: Vec::new(), items: vec![structured_event()],
next_cursor: None, 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 { fn empty_metrics() -> crate::MetricsSnapshot {
serde_json::from_value(serde_json::json!({ serde_json::from_value(serde_json::json!({
"ready":true,"reconnects":0,"active_sessions":0,"active_tasks":0, "ready":true,"reconnects":0,"active_sessions":0,"active_tasks":0,
@@ -116,6 +141,96 @@ async fn snapshot_refresh_is_transport_neutral_and_bounded() {
"running" "running"
); );
assert_eq!(transport.0.lock().expect("requests").len(), 8); 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] #[test]
@@ -142,7 +257,7 @@ fn every_screen_renders_without_a_terminal_at_small_unicode_and_mono_sizes() {
color: false, color: false,
}); });
assert!(!output.is_empty()); assert!(!output.is_empty());
assert!(output.lines().count() <= 4); assert!(output.lines().count() <= 5);
assert!( assert!(
output output
.lines() .lines()
@@ -196,6 +311,21 @@ fn every_global_command_has_a_keyboard_path() {
assert_eq!(app.command_shortcut('s'), Some(OperatorCommand::Shutdown)); 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] #[test]
fn transport_errors_are_safe_display_text() { fn transport_errors_are_safe_display_text() {
struct Broken; struct Broken;

View File

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

View File

@@ -108,3 +108,23 @@ fn dotenv_import_is_explicit_and_preferences_panel_never_renders_secrets() {
let _ = tui.reduce(TuiInput::PreferenceSave); let _ = tui.reduce(TuiInput::PreferenceSave);
fs::remove_dir_all(directory).unwrap(); 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();
}

View File

@@ -1,8 +1,9 @@
# Grid-agent operator TUI # Grid-agent operator TUI
The operator TUI is a pure-Rust, cross-platform view of the versioned control The operator TUI is a pure-Rust, cross-platform Ratatui dashboard over the
plane. Embedded and split modes use the same reducer, snapshots, commands, and versioned control plane. Embedded and split modes use the same reducer,
authenticated control requests; the UI never reads private runtime state. snapshots, widgets, commands, and authenticated control requests; the UI never
reads private runtime state. Standalone preferences uses the same Ratatui form.
Run an embedded UI with a live-grid integrated configuration: Run an embedded UI with a live-grid integrated configuration:
@@ -31,19 +32,33 @@ Up/Down to select, Enter to edit/commit, `w` to save, and Esc to cancel. Changes
to connection or authorization settings apply after restart. A one-time to connection or authorization settings apply after restart. A one-time
`--import-env /path/to/.env` migrates legacy `GRID_*` and `OPENAPI_*` values. `--import-env /path/to/.env` migrates legacy `GRID_*` and `OPENAPI_*` values.
Use Tab/arrow keys to change panels and scroll. `r` refreshes; `p`/`u` pause or The Overview dashboard shows readiness, region/pose, behavior, build and visual
resume; `f` reconnects the grid; `a`/`d` approve or deny the first pending work, approvals, and recent failures. Queues & budgets shows real queue and
approval; `x` cancels the newest visible action; `e` expires the first visible rate-limit gauges, active work, outcome counts, dropped events, aggregate
session; `t` toggles the first roaming schedule; `s` requests shutdown; and latency, and a bounded 120-refresh history. Sessions, roaming schedules,
`q` or Ctrl-C exits. Mutations other than pause/resume require confirmation, approvals, timeline/audit, errors, diagnostics, health, and preferences retain
with shutdown marked high risk. Exiting or crashing a split TUI does not stop focused tables or panels and contextual detail. Missing backend data is marked
the service. unavailable rather than estimated.
Rendering is terminal-independent, Unicode display-width aware, responsive to Use Tab/Shift-Tab or Left/Right to change panels and Up/Down to select rows.
resize events, and monochrome when `NO_COLOR` is set or `TERM=dumb`. Input uses `/` searches event, audit, correlation, time, duration, result, and reason data;
a bounded queue. Snapshot ticks coalesce while a request is slow, and timeline Enter applies the search and Esc clears it. `r` refreshes; `p`/`u` pause or
storage is bounded. The terminal guard restores raw mode, cursor visibility, resume; `f` reconnects the grid; `a`/`d` approve or deny the selected approval;
and the alternate screen on success, error, Ctrl-C, or panic unwinding. `x` cancels the newest visible action; `e` expires the selected session; `t`
toggles the selected roaming schedule; `s` requests shutdown; and `q` or
Ctrl-C exits. Mutations other than pause/resume require confirmation, with
shutdown marked high risk. Exiting or crashing a split TUI does not stop the
service.
Ratatui's Crossterm backend provides buffered/diff rendering without clearing
the full screen on every frame. Layouts respond to resize and collapse to a
compact status view on very small terminals; focus and failure markers remain
visible without color when `NO_COLOR` is set or `TERM=dumb`. Input, refresh
work, results, event history, errors, and performance history are all bounded.
Slow snapshots run outside the input reducer, and refresh ticks are dropped
when the bounded worker queue is full. The terminal guard restores raw mode,
cursor visibility, and the alternate screen on success, error, Ctrl-C, or
panic unwinding.
Diagnostic panels show only explicitly captured redacted envelopes. Prompt Diagnostic panels show only explicitly captured redacted envelopes. Prompt
and response content, API keys, grid passwords, operator tokens, capability and response content, API keys, grid passwords, operator tokens, capability
@@ -52,7 +67,8 @@ URLs, and model reasoning have no TUI representation.
Focused verification: Focused verification:
```sh ```sh
cargo test --locked -p metacrate-grid-agent --lib tui_tests cargo test --locked -p metacrate-grid-agent tui --lib
cargo test --locked -p metacrate-grid-agent --test preferences_config
cargo test --locked -p metacrate-grid-agent --test dependency_policy cargo test --locked -p metacrate-grid-agent --test dependency_policy
cargo clippy --locked -p metacrate-grid-agent --all-targets --features live-grid -- -D warnings cargo clippy --locked -p metacrate-grid-agent --all-targets --features live-grid -- -D warnings
RUSTDOCFLAGS="-D warnings" cargo doc --locked -p metacrate-grid-agent --no-deps RUSTDOCFLAGS="-D warnings" cargo doc --locked -p metacrate-grid-agent --no-deps