From 8542f8439494324c57bfe2d6df6e9973e90cff58 Mon Sep 17 00:00:00 2001 From: arlo Date: Wed, 14 Oct 2026 18:38:39 +0800 Subject: [PATCH 1/2] fix(kit): resolve timeline component names before registration Performance and component-emit events can fire before the component is registered (e.g. during init and the first render), so the registry lookup failed and the timeline showed "Unknown Component". Fall back to reading the name from the instance, matching the component tree. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/kit/src/runtime/registry.ts | 2 +- packages/kit/src/runtime/timeline.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/kit/src/runtime/registry.ts b/packages/kit/src/runtime/registry.ts index db42bc89b..95db07259 100644 --- a/packages/kit/src/runtime/registry.ts +++ b/packages/kit/src/runtime/registry.ts @@ -462,7 +462,7 @@ function readAppName(app: object): string | undefined { return component ? readStringProperty(component, 'name') : undefined } -function readComponentName(instance: object): string { +export function readComponentName(instance: object): string { const type = readObjectProperty(instance, 'type') if (type) { const name = diff --git a/packages/kit/src/runtime/timeline.ts b/packages/kit/src/runtime/timeline.ts index 8d4a797b9..a0b521058 100644 --- a/packages/kit/src/runtime/timeline.ts +++ b/packages/kit/src/runtime/timeline.ts @@ -2,6 +2,7 @@ import type { EncodedValue, ValueHandleRegistry } from '../codec' import type { AppId, InstanceRef } from './types' import type { DevtoolsRuntime } from './runtime' import { encodeValue } from '../codec' +import { readComponentName } from './registry' interface PerformanceStartEvent { type: 'perf:start' @@ -148,7 +149,7 @@ export function createRuntimeTimeline( const componentRecord = component ? runtime.registry.getComponent(component, event.appId) : undefined - const componentName = componentRecord?.name ?? 'Unknown Component' + const componentName = componentRecord?.name ?? readComponentName(event.instance) dispatchTimelineEvent(runtime, handles, state, { appId: event.appId, @@ -236,7 +237,7 @@ function addPerformanceStart( const componentRecord = componentId ? runtime.registry.getComponent(componentId, event.appId) : undefined - const componentName = componentRecord?.name ?? 'Unknown Component' + const componentName = componentRecord?.name ?? readComponentName(event.instance) const groupId = state.groupSeed++ // The registry ID can change from undefined to an ID between start and end. const groupKey = `${event.appId}:${event.phase}` @@ -286,7 +287,7 @@ function addPerformanceEnd( const componentRecord = componentId ? runtime.registry.getComponent(componentId, event.appId) : undefined - const componentName = componentRecord?.name ?? 'Unknown Component' + const componentName = componentRecord?.name ?? readComponentName(event.instance) const duration = Math.max(0, event.timestamp - group.startTime) groups!.delete(groupKey) From 60c76dd6f2024fbd3a784d8ec24da7ccc66e2b81 Mon Sep 17 00:00:00 2001 From: arlo Date: Wed, 14 Oct 2026 20:46:30 +0800 Subject: [PATCH 2/2] chore: update tests --- .../unit/runtime/timeline-rate-limit.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/unit/runtime/timeline-rate-limit.test.ts b/tests/unit/runtime/timeline-rate-limit.test.ts index a6ce06a33..596221ae4 100644 --- a/tests/unit/runtime/timeline-rate-limit.test.ts +++ b/tests/unit/runtime/timeline-rate-limit.test.ts @@ -5,6 +5,53 @@ import * as codec from '../../../packages/kit/src/codec' afterEach(() => vi.useRealTimers()) +it('names component events before component registration', async () => { + const runtime = createDevtoolsRuntime() + try { + const fixture = createComponentTreeFixture(2) + runtime.dispatch({ + type: 'app:init', + app: fixture.app, + version: '3.5.0', + vueTypes: {}, + time: 1, + }) + const instance = fixture.instances[1]! + expect(runtime.registry.getComponentId(instance)).toBeUndefined() + const events = vi.fn() + runtime.subscribe('timeline:eventAdded', events) + runtime.dispatch({ + type: 'component:emit', + appId: 'app:0', + instance, + event: 'update', + args: [], + time: 2, + }) + await runtime.scheduler.flush() + expect(events).toHaveBeenCalledExactlyOnceWith( + expect.objectContaining({ + layerId: 'component-event', + title: 'update', + subtitle: 'by FixtureComponent1', + data: expect.objectContaining({ + preview: expect.arrayContaining([ + expect.objectContaining({ + key: 'component', + value: expect.objectContaining({ + kind: 'custom', + display: 'FixtureComponent1', + }), + }), + ]), + }), + }), + ) + } finally { + runtime.dispose() + } +}) + it('pairs performance events across component registration', async () => { const runtime = createDevtoolsRuntime() try { @@ -17,6 +64,7 @@ it('pairs performance events across component registration', async () => { time: 1, }) const instance = fixture.instances[1]! + expect(runtime.registry.getComponentId(instance)).toBeUndefined() const events = vi.fn() runtime.subscribe('timeline:eventAdded', events) runtime.dispatch({ @@ -39,6 +87,14 @@ it('pairs performance events across component registration', async () => { await runtime.scheduler.flush() expect(events).toHaveBeenCalledTimes(2) expect(events.mock.calls[1]![0].groupId).toBe(events.mock.calls[0]![0].groupId) + for (const [event] of events.mock.calls) { + expect(event.title).toBe('FixtureComponent1') + expect(event.data).toMatchObject({ + preview: expect.arrayContaining([ + { key: 'component', value: { kind: 'string', value: 'FixtureComponent1' } }, + ]), + }) + } } finally { runtime.dispose() }