part = 4 / 5
Panels for a three-level world
Take the premise seriously: sessions, traces, and spans are all objects a user should be able to reason about, debug, and richly annotate — with equal standing. Almost every layout decision follows from that premise plus two rules. Wireframes below.
The diagnosis, briefly
Phoenix's current organization treats each level as a different place: a traces table you leave to enter a trace page, a sessions table you leave to enter a session page, a span drawer that opens inside either. The inspector's shape changes at each hop, annotation is offered only at the span level (Part 1), and "where am I in the hierarchy" lives in the browser's back button. The individual screens are fine; the seams between levels are where users lose state, and the seams are exactly where AI debugging happens — you find the bad turn in a session, drop into its trace, read one span, and pop back up to judge the conversation. That loop crosses all three levels every time. An interface that makes level-crossing expensive taxes the core loop.
Two rules that generate the layout
-
Selection is a path, not a page. The selected object is always
session / trace / span(with any suffix possibly empty). Moving between levels mutates the path; it never navigates away. The path is always visible and every segment is clickable. - The canvas changes per level; the inspector never does. What a session looks like (a transcript), what a trace looks like (a waterfall), and what a span looks like (payloads) are legitimately different projections — that's the canvas's job. But inspecting anything — identity, metadata, annotations, evals — is the same activity at every level, so it deserves one persistent panel with one shape, aimed at whatever the path points to.
Rule two is the one that fixes annotation. If the inspector is level-agnostic and always states its target, then "Annotate" on a session annotates the session — because the session is what's selected, and the panel says so. The current bug (turn-level annotate button writes to the root span) can't be expressed in this layout without the label visibly lying.
Scheme A is the recommendation. Why:
- The navigator makes the hierarchy legible for free. A collapsible tree — turns at the top level, spans nested beneath — is a persistent, glanceable answer to "what is this thing made of," which is the question the current UI answers only by drilling. It also gives orphan spans and multi-root traces (Parts 1 and 5) an honest place to appear, rather than being a rendering surprise.
- One canvas, three projections. Transcript at session altitude, waterfall/tree at trace altitude, payload viewer at span altitude — with the level tabs doubling as the zoom control. This is the same "projections of one dataset" idea the whole series is built on, made into the navigation metaphor itself.
- The inspector is where equal-annotatability becomes real. Same tabs at every altitude (Summary · Annotations · Metadata), same annotation composer, an explicit target chip ("annotating: session abc-123"), and roll-up counts from lower levels so a session shows the verdicts accumulated on its traces and spans.
Scheme B — a single full-bleed canvas with a breadcrumb zoom stack — is the same information architecture minus the persistent tree, and is the right compact variant (small screens, embedded views). It costs you the always-visible structural map, which matters most for the pathological traces where you need it most. Scheme C is today's model, included because seeing "navigation = page transitions, inspector changes shape at each hop, annotation exists at one level" drawn as boxes makes the critique concrete.
The annotation rail: one component, three altitudes
The piece to build once and mount everywhere. Identity of layout across levels is what teaches users that sessions, traces, and spans are equally first-class — no documentation does this, panels do.
project_session_annotations, Part 1). The rail is a
frontend unification project: one composer, one list, one summary component,
parameterized by target type.
Details that make or break it
-
Say what the object is made of, in the UI's own voice. A one-line
subtitle — "turn 2 of 3 · trace b911 · 9 spans" — does more to teach the data model
than any docs page. Where the model is derived, the interface should quietly admit
it: a session with no
session.id-bearing spans isn't an error state, it's a teachable empty state. - Degrade honestly at the edges. Missing root span? The trace's face in lists should say "no root span (2 orphans)" — not render blank. In-flight trace? Label it in-progress and show the partial forest (Part 2's scrubber is basically this feature's spec). Session-id conflict swallowed at ingest? Surface it as a warning chip on the session. Every edge case Parts 1–2 catalogued is an empty state to design, not a bug to hide.
- Keyboard-native level crossing. If the core loop crosses levels constantly, up/down (siblings) and left/right (altitude) should walk the path without touching the mouse. This is cheap once selection-is-a-path is real, and impossible before.