Files
zzg 9bc48467a4 Add the HTTP server, embedded UI, config, and CLI
Three views: a trace list, the trace detail waterfall with its correlated
transcript, and an aggregate query explorer. Vanilla JS in a go:embed'd
directory -- no bundler, no npm, and no way for the API and UI to drift.

Several fixes came directly from screenshotting the running app in Chrome:

- model.Span had no JSON tags, so Go marshalled SpanID while the client read
  span_id. The inspector sat on "Loading..." forever. Tagged the model and
  made Kind/Status marshal as names, so the client keeps no parallel enum.
- replaceChildren stringifies null rather than skipping it, rendering a
  literal "null" in the trace header.
- Cost was empty for every trace: it is reported on the api_request LOG
  record, never on a span, and the list scanned only spans.
- Every tool call rendered three times, because Claude Code emits tool,
  tool.execution, and tool.blocked_on_user for one logical call. Wrapper
  spans that restate their parent are now folded, with a count on the parent.
- The list showed "claude_code.interaction" on all 40 rows while the prompt
  that distinguishes runs sat in a subtitle. The prompt now leads.
- A keyless log record was placed by timestamp containment, filing the user's
  prompt under whichever model call started at the same microsecond. It now
  prefers its own span_id, which for keyless records is the right signal.
- The Explore chart plotted the first numeric column, so a "slowest tools"
  query rendered call counts. Results now carry the ORDER BY measure.
- An unnamed category (the wrapper spans) crushed every named bar; unnamed
  rows are left to the table with a note.
2026-09-15 21:55:49 -07:00
..