fix(ui): remove intra-trace coalescing — preserve every stage for inspection

PR-Agent flagged that the trace-child coalesce was hiding legitimate
repeated stage emissions: a request that retries an upstream call or
emits the same stage twice for any reason would show only the first
occurrence behind a `×N` badge, with subsequent attempts no longer
selectable or inspectable.

The original noise problem motivating intra-trace coalesce was the
149-stage `web-server:http` self-polling trace, but that's already
hidden by default via the `hideDashboardInternals` filter. Users who
opt in to see internals are deliberately debugging the dashboard and
SHOULD see all 149 stages — collapsing them defeats the opt-in.

Drop `coalesceChildren` entirely. Trace children render uncoalesced
so retries, duplicated stages, and multi-attempt traces stay
individually inspectable. Standalone-leaf coalesce stays — that's
the actual user-facing dedup the PR was about.

Tests: drop the 7 `coalesceChildren` cases; keep 16 covering
`deriveStageHint` and `deriveTraceGroups`.

Refs #1138, #1151
This commit is contained in:
Tam Nhu Tran committed 2026-04-30 16:28:42 -04:00
1 parent 6ada2b243e
commit 2ddc5d5afd
3 files changed
+22 -104

No files matched your search

@@ -1,7 +1,6 @@
import { describe, expect, it } from 'vitest';
import type { LogsEntry } from '@/lib/api-client';
import {
coalesceChildren,
deriveStageHint,
deriveTraceGroups,
type LeafItem,
@@ -51,53 +50,6 @@ describe('deriveStageHint', () => {
});
});
describe('coalesceChildren', () => {
it('returns empty array for empty input', () => {
expect(coalesceChildren([])).toEqual([]);
});
it('passes through a single child as count=1', () => {
const e = entry({ id: '1', timestamp: 't', event: 'a.b' });
expect(coalesceChildren([e])).toEqual([{ head: e, count: 1 }]);
});
it('coalesces two adjacent identical children', () => {
const a = entry({ id: '1', timestamp: 't1', event: 'a.b', message: 'm' });
const b = entry({ id: '2', timestamp: 't2', event: 'a.b', message: 'm' });
const result = coalesceChildren([a, b]);
expect(result).toHaveLength(1);
expect(result[0]?.count).toBe(2);
expect(result[0]?.head).toBe(a);
});
it('keeps children with different messages distinct (round-1 fix)', () => {
const a = entry({ id: '1', timestamp: 't1', event: 'auth.ok', message: 'alice' });
const b = entry({ id: '2', timestamp: 't2', event: 'auth.ok', message: 'bob' });
expect(coalesceChildren([a, b])).toHaveLength(2);
});
it('keeps children with different sources distinct (round-2 fix)', () => {
const a = entry({ id: '1', timestamp: 't1', source: 'svc-a', event: 'e' });
const b = entry({ id: '2', timestamp: 't2', source: 'svc-b', event: 'e' });
expect(coalesceChildren([a, b])).toHaveLength(2);
});
it('keeps children with different stages distinct', () => {
const a = entry({ id: '1', timestamp: 't1', stage: 'route', event: 'e' });
const b = entry({ id: '2', timestamp: 't2', stage: 'dispatch', event: 'e' });
expect(coalesceChildren([a, b])).toHaveLength(2);
});
it('only collapses adjacent duplicates — interleaving breaks the run', () => {
const a = entry({ id: '1', timestamp: 't1', event: 'a' });
const b = entry({ id: '2', timestamp: 't2', event: 'b' });
const c = entry({ id: '3', timestamp: 't3', event: 'a' });
const result = coalesceChildren([a, b, c]);
// a, b, a — three distinct runs of count 1 each
expect(result.map((r) => r.count)).toEqual([1, 1, 1]);
});
});
describe('deriveTraceGroups', () => {
it('returns empty array for empty input', () => {
expect(deriveTraceGroups([])).toEqual([]);