From d1c32a9b49b9e173ae02e14e54b89879f666766b Mon Sep 17 00:00:00 2001 From: betegon Date: Thu, 12 Mar 2026 08:34:39 +0100 Subject: [PATCH 1/3] fix(trace): show span IDs in trace view and fix event_id mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The trace detail API returns `event_id` instead of `span_id` on each span, so all span IDs rendered as `undefined` in the span tree and `span view` lookups always failed. Normalize the response in `getDetailedTrace` by copying `event_id` → `span_id`, and append the span ID to each tree line so users can copy it into `span view`. Co-Authored-By: Claude Opus 4.6 --- src/lib/api-client.ts | 19 ++++++++++++++++++- src/lib/formatters/human.ts | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts index aa438a22..15c188f9 100644 --- a/src/lib/api-client.ts +++ b/src/lib/api-client.ts @@ -1503,7 +1503,24 @@ export async function getDetailedTrace( }, } ); - return data; + return data.map(normalizeTraceSpan); +} + +/** + * The trace detail API (`/trace/{id}/`) returns each span's unique identifier + * as `event_id` rather than `span_id`. The value is the same 16-hex-char span + * ID that `parent_span_id` references on child spans. We copy it to `span_id` + * so the rest of the codebase can use a single, predictable field name. + */ +function normalizeTraceSpan(span: TraceSpan): TraceSpan { + const normalized = { ...span }; + if (!normalized.span_id && normalized.event_id) { + normalized.span_id = normalized.event_id; + } + if (normalized.children) { + normalized.children = normalized.children.map(normalizeTraceSpan); + } + return normalized; } /** Fields to request from the transactions API */ diff --git a/src/lib/formatters/human.ts b/src/lib/formatters/human.ts index 765bd6e8..7beebae7 100644 --- a/src/lib/formatters/human.ts +++ b/src/lib/formatters/human.ts @@ -1122,6 +1122,8 @@ function formatSpanSimple(span: TraceSpan, opts: FormatSpanOptions): void { line += ` ${muted(`(${prettyMs(durationMs)})`)}`; } + line += ` ${muted(span.span_id)}`; + lines.push(line); if (currentDepth < maxDepth) { From dff8027fe29c4d6b9f27c741e8479de29b1d1214 Mon Sep 17 00:00:00 2001 From: betegon Date: Thu, 12 Mar 2026 10:39:13 +0100 Subject: [PATCH 2/3] fix(trace): add fallback for missing span_id in trace view Prevent rendering literal "undefined" when both span_id and event_id are absent on a span. Co-Authored-By: Claude Opus 4.6 --- src/lib/formatters/human.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/formatters/human.ts b/src/lib/formatters/human.ts index 7beebae7..30442411 100644 --- a/src/lib/formatters/human.ts +++ b/src/lib/formatters/human.ts @@ -1122,7 +1122,7 @@ function formatSpanSimple(span: TraceSpan, opts: FormatSpanOptions): void { line += ` ${muted(`(${prettyMs(durationMs)})`)}`; } - line += ` ${muted(span.span_id)}`; + line += ` ${muted(span.span_id ?? "")}`; lines.push(line); From 9459981fda62c7e41a928cdf4ced3905551a717f Mon Sep 17 00:00:00 2001 From: betegon Date: Thu, 12 Mar 2026 10:44:30 +0100 Subject: [PATCH 3/3] test(trace): add tests for normalizeTraceSpan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Export the function and add unit tests covering event_id→span_id fallback, existing span_id preservation, and child recursion. Co-Authored-By: Claude Opus 4.6 --- src/lib/api-client.ts | 2 +- .../lib/api-client.normalizeTraceSpan.test.ts | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 test/lib/api-client.normalizeTraceSpan.test.ts diff --git a/src/lib/api-client.ts b/src/lib/api-client.ts index 15c188f9..0bc06842 100644 --- a/src/lib/api-client.ts +++ b/src/lib/api-client.ts @@ -1512,7 +1512,7 @@ export async function getDetailedTrace( * ID that `parent_span_id` references on child spans. We copy it to `span_id` * so the rest of the codebase can use a single, predictable field name. */ -function normalizeTraceSpan(span: TraceSpan): TraceSpan { +export function normalizeTraceSpan(span: TraceSpan): TraceSpan { const normalized = { ...span }; if (!normalized.span_id && normalized.event_id) { normalized.span_id = normalized.event_id; diff --git a/test/lib/api-client.normalizeTraceSpan.test.ts b/test/lib/api-client.normalizeTraceSpan.test.ts new file mode 100644 index 00000000..41c66e04 --- /dev/null +++ b/test/lib/api-client.normalizeTraceSpan.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, test } from "bun:test"; +import { normalizeTraceSpan } from "../../src/lib/api-client.js"; + +describe("normalizeTraceSpan", () => { + test("copies event_id to span_id when span_id is missing", () => { + const span = { event_id: "abc123", start_timestamp: 0 } as Parameters< + typeof normalizeTraceSpan + >[0]; + const result = normalizeTraceSpan(span); + expect(result.span_id).toBe("abc123"); + }); + + test("preserves existing span_id", () => { + const result = normalizeTraceSpan({ + span_id: "existing", + event_id: "other", + start_timestamp: 0, + }); + expect(result.span_id).toBe("existing"); + }); + + test("recurses into children", () => { + const result = normalizeTraceSpan({ + span_id: "parent", + start_timestamp: 0, + children: [{ event_id: "child1", start_timestamp: 1 } as any], + }); + expect(result.children?.[0]?.span_id).toBe("child1"); + }); +});