spanDetail.test.tsxfeat: draw measured tool calls as real bars, and stop the filter inventing dead time Tool spans now render as bars scaled to the root window when timingSource is 'measured', keeping the minimum clickable floor; 'completion-only' rows keep the point marker. The discriminator is the flag, never durationMs === 0, because a genuinely instantaneous measured call is now representable and must look different from one that was never measured. The stale copy was inverted, not merely incomplete: the module claimed `created_at` was a start ("Tool calls store only a start time") when it is the completion, so the marker was drawn at an end and labelled a start. The labels are fixed too, not just the shape. The gap indicator was structurally incapable of firing on any execution whose children are tool calls — they were all zero-width, so coverage was empty and it returned early. That is the whole leaf tier of every trace. Measured tool calls now count as coverage and the bands appear where they were always meant to. The design's "usually the model thinking" is documented as an upper bound rather than an identity: a mixed trace still hides un-instrumented tool time inside the gap, only direct children count, and framework latency lands there too. One live correctness bug found while verifying that: gaps were computed from the FILTERED span list. Harmless while tools covered nothing, but with real widths, filtering to one tool deleted a covering call and fabricated ~2.8s of thinking that never happened. Gaps are now computed over the unfiltered spans — a gap is a fact about the run, not about the view. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 11, 2026, 3:05 PMtraceFixture.tsfeat: draw measured tool calls as real bars, and stop the filter inventing dead time Tool spans now render as bars scaled to the root window when timingSource is 'measured', keeping the minimum clickable floor; 'completion-only' rows keep the point marker. The discriminator is the flag, never durationMs === 0, because a genuinely instantaneous measured call is now representable and must look different from one that was never measured. The stale copy was inverted, not merely incomplete: the module claimed `created_at` was a start ("Tool calls store only a start time") when it is the completion, so the marker was drawn at an end and labelled a start. The labels are fixed too, not just the shape. The gap indicator was structurally incapable of firing on any execution whose children are tool calls — they were all zero-width, so coverage was empty and it returned early. That is the whole leaf tier of every trace. Measured tool calls now count as coverage and the bands appear where they were always meant to. The design's "usually the model thinking" is documented as an upper bound rather than an identity: a mixed trace still hides un-instrumented tool time inside the gap, only direct children count, and framework latency lands there too. One live correctness bug found while verifying that: gaps were computed from the FILTERED span list. Harmless while tools covered nothing, but with real widths, filtering to one tool deleted a covering call and fabricated ~2.8s of thinking that never happened. Gaps are now computed over the unfiltered spans — a gap is a fact about the run, not about the view. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 11, 2026, 3:05 PMtraceKeyboard.test.tsxfeat: the trace viewer — an Axiom-style waterfall over agent runs (design: curated-agenda phase 7) A run reads like a distributed trace: an execution is a span, a dispatched subagent is a child span, a tool call is a leaf span, all on one time axis scaled to the root's window so overlapping siblings read as genuinely parallel and a serialized fan-out reads as a staircase. Route at /settings/agentExecutions/:runId, reachable from every execution list row and from a paste-a-run-id box. Zero-duration tool spans are drawn honestly. agent_tool_calls records no completion timestamp, so every tool span has durationMs 0; rather than floor it into a bar that implies a duration nobody measured, it renders as a point marker with the duration column showing an em dash and the reason stated on hover. The minimum-bar-width floor still exists, but for genuinely short executions, which is the real sub-pixel case. Gap indicators mark dead time inside a parent not covered by any child — usually the model thinking, and where a run's latency actually goes. Tool-call points cover nothing, so they cannot fake a gap away. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 9, 2026, 5:21 PMtraceWaterfall.test.tsxfeat: draw measured tool calls as real bars, and stop the filter inventing dead time Tool spans now render as bars scaled to the root window when timingSource is 'measured', keeping the minimum clickable floor; 'completion-only' rows keep the point marker. The discriminator is the flag, never durationMs === 0, because a genuinely instantaneous measured call is now representable and must look different from one that was never measured. The stale copy was inverted, not merely incomplete: the module claimed `created_at` was a start ("Tool calls store only a start time") when it is the completion, so the marker was drawn at an end and labelled a start. The labels are fixed too, not just the shape. The gap indicator was structurally incapable of firing on any execution whose children are tool calls — they were all zero-width, so coverage was empty and it returned early. That is the whole leaf tier of every trace. Measured tool calls now count as coverage and the bands appear where they were always meant to. The design's "usually the model thinking" is documented as an upper bound rather than an identity: a mixed trace still hides un-instrumented tool time inside the gap, only direct children count, and framework latency lands there too. One live correctness bug found while verifying that: gaps were computed from the FILTERED span list. Harmless while tools covered nothing, but with real widths, filtering to one tool deleted a covering call and fabricated ~2.8s of thinking that never happened. Gaps are now computed over the unfiltered spans — a gap is a fact about the run, not about the view. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 11, 2026, 3:05 PM