From d28244ee1d9964838de5f8683f99ce4778719fa9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 8 May 2026 09:09:34 +0000 Subject: [PATCH] soft-trace-viewer: search across traces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `/api/search?q=...` endpoint and a search box in the embedded HTML so operators can find traces by content (e.g. find every run where depot's gate denied, by querying "Deny" or "grid_budget"). Backend: - New `GET /api/search?q=` route. Naive case-insensitive substring scan across each trace's serialized JSON. Returns `{"query": "...", "scanned": N, "results": [{run_id, snippet, match_offset}, ...]}`. Snippet is ~80 characters centered on the match, snapped to UTF-8 boundaries. - Empty `q` returns `{results: [], query: ""}` rather than 400 — UI sends the empty case during typing. - Minimal `application/x-www-form-urlencoded` decoder for `+` → space and `%XX` → byte. Sufficient for the search box; not a full URL parser. For Phase 1 trace volumes (~1 KB per trace, dozens of traces) the naive scan is fast enough. If the store grows past a few thousand traces, this would benefit from `lex-search` (lex 0.3 #224) or a precomputed inverted index. Frontend: - Search input in the header. 200 ms debounced; calls `/api/search?q=` and dims non-matching sidebar entries. Matching entries get a snippet line below the run-id with the matched substring ``-highlighted. - Result counter: "N / M" (matches / scanned) next to the input. Tests (4 new in `crates/soft-trace-viewer/tests/smoke.rs`): - search with no `q` returns empty list - search on empty store returns zero results - URL-decoding works for both `%20` and `+` - end-to-end: drive a real soft-agent runner, persist a trace containing a known token, then verify search finds it. Negative case verifies an unrelated query returns zero matches. cargo fmt clean, cargo clippy --workspace --all-targets -- -D warnings clean, full workspace test suite: 102 passed / 0 failed. --- Cargo.lock | 1 + crates/soft-trace-viewer/Cargo.toml | 6 +- crates/soft-trace-viewer/src/index.html | 77 +++++++++++++- crates/soft-trace-viewer/src/main.rs | 129 +++++++++++++++++++++- crates/soft-trace-viewer/tests/smoke.rs | 136 ++++++++++++++++++++++++ 5 files changed, 342 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c9f123..9ce979e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1839,6 +1839,7 @@ dependencies = [ "lex-store", "serde", "serde_json", + "soft-agent", "tempfile", "tiny_http", "ureq 2.12.1", diff --git a/crates/soft-trace-viewer/Cargo.toml b/crates/soft-trace-viewer/Cargo.toml index ec9b27d..c4b2789 100644 --- a/crates/soft-trace-viewer/Cargo.toml +++ b/crates/soft-trace-viewer/Cargo.toml @@ -20,5 +20,7 @@ serde_json = { workspace = true } tiny_http = { workspace = true } [dev-dependencies] -tempfile = "3" -ureq = { workspace = true } +tempfile = "3" +ureq = { workspace = true } +# End-to-end search test produces a real trace via soft-agent's runner. +soft-agent = { path = "../soft-agent" } diff --git a/crates/soft-trace-viewer/src/index.html b/crates/soft-trace-viewer/src/index.html index 1b6491a..566113f 100644 --- a/crates/soft-trace-viewer/src/index.html +++ b/crates/soft-trace-viewer/src/index.html @@ -20,6 +20,12 @@ display: flex; align-items: baseline; gap: 12px; } header h1 { font-size: 14px; margin: 0; font-weight: 600; } header .meta { color: var(--muted); font-size: 12px; } + header input.search { margin-left: auto; min-width: 240px; padding: 4px 8px; + font-size: 12px; background: var(--bg); color: var(--fg); + border: 1px solid var(--border); border-radius: 3px; + font-family: monospace; } + header input.search:focus { outline: none; border-color: var(--accent); } + header .search-count { color: var(--muted); font-size: 11px; min-width: 60px; } main { display: grid; grid-template-columns: 280px 1fr; height: calc(100vh - 38px); } #sidebar { border-right: 1px solid var(--border); overflow-y: auto; padding: 8px 0; background: var(--panel); } @@ -28,6 +34,11 @@ #sidebar .trace:hover { color: var(--fg); background: rgba(88, 166, 255, 0.05); } #sidebar .trace.active { color: var(--accent); border-left-color: var(--accent); background: rgba(88, 166, 255, 0.08); } + #sidebar .trace.dimmed { opacity: 0.25; } + #sidebar .trace .snippet { display: block; font-size: 10px; color: var(--muted); + margin-top: 2px; word-break: break-all; } + #sidebar .trace .snippet mark { background: rgba(88, 166, 255, 0.25); + color: var(--fg); padding: 0; } #content { overflow-y: auto; padding: 16px 24px; } .placeholder { color: var(--muted); font-style: italic; } .node { margin: 4px 0; } @@ -56,12 +67,19 @@

soft trace viewer

click a run id to load its trace + +
Select a trace from the sidebar.