Log tabs with a daemon I/O view, and Playwright UI tests
The log view splits into All / Daemon I/O / Scans / HTTP. Daemon I/O is the control protocol itself, logged where every command funnels through so it covers socket clients, the CLI and the web UI alike. stderr and the in-app buffer now have separate filters, so the UI can keep debug detail the terminal should not carry. Playwright drives a real browser against a daemon on fixture feeds. Eight tests, each mapping to a bug that reached a user -- the Rust tests and the stub-DOM smoke test cannot see a wrong selector or a dead handler. It immediately found one: OPML folders rendered expanded by default, because the code stored closed groups, so any folder never toggled counted as open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
This commit is contained in:
@@ -149,6 +149,15 @@ impl Emitter {
|
||||
}
|
||||
|
||||
if let Some(tx) = &self.tx {
|
||||
// The outbound half of the protocol, as it goes on the wire. Progress is the
|
||||
// high-volume one, so it sits at debug.
|
||||
if let Ok(json) = serde_json::to_string(&e) {
|
||||
if matches!(e, Event::Progress { .. }) {
|
||||
tracing::debug!(target: "ipx::io", "<- {json}");
|
||||
} else {
|
||||
tracing::info!(target: "ipx::io", "<- {json}");
|
||||
}
|
||||
}
|
||||
// An error here only means nobody is listening yet.
|
||||
let _ = tx.send(e.clone());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use tracing_subscriber::Layer;
|
||||
use tracing_subscriber::layer::Context;
|
||||
|
||||
/// Kept small enough to be cheap to hold and to serialise in one response.
|
||||
const CAPACITY: usize = 2000;
|
||||
const CAPACITY: usize = 5000;
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize)]
|
||||
pub struct LogLine {
|
||||
|
||||
25
src/main.rs
25
src/main.rs
@@ -126,13 +126,21 @@ async fn main() -> Result<()> {
|
||||
{
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::Layer;
|
||||
// Two filters, deliberately different. stderr follows IPX_LOG; the in-app buffer
|
||||
// keeps debug as well, so the log view can show protocol traffic and routine
|
||||
// skips that would be noise on a terminal. IPX_UI_LOG overrides it.
|
||||
let stderr_filter = tracing_subscriber::EnvFilter::try_from_env("IPX_LOG")
|
||||
.unwrap_or_else(|_| "ipx=info".into());
|
||||
let ui_filter = tracing_subscriber::EnvFilter::try_from_env("IPX_UI_LOG")
|
||||
.unwrap_or_else(|_| "ipx=debug".into());
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_env("IPX_LOG")
|
||||
.unwrap_or_else(|_| "ipx=info".into()),
|
||||
tracing_subscriber::fmt::layer()
|
||||
.with_writer(std::io::stderr)
|
||||
.with_filter(stderr_filter),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
|
||||
.with(logbuf::RingLayer)
|
||||
.with(logbuf::RingLayer.with_filter(ui_filter))
|
||||
.init();
|
||||
}
|
||||
|
||||
@@ -282,7 +290,14 @@ async fn daemon(
|
||||
biased;
|
||||
_ = stop.changed() => break,
|
||||
Some(cmd) = rx_cmd.recv() => {
|
||||
tracing::info!(?cmd, "command from a client");
|
||||
// Both halves of the protocol are logged under one target so the UI can
|
||||
// show the conversation on its own: this is everything arriving, whatever
|
||||
// the source -- a socket client, the CLI proxying, or the web UI.
|
||||
tracing::info!(
|
||||
target: "ipx::io",
|
||||
"-> {}",
|
||||
serde_json::to_string(&cmd).unwrap_or_else(|_| format!("{cmd:?}"))
|
||||
);
|
||||
if !until_stopped(&ctx, &rx_stop, run(&ctx, cmd)).await {
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user