mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 03:59:41 +00:00
fix(hydration-timing): report list hydration relative to the foreground attempt (#1813)
The headline listVisibleMs was boot-relative, so a navigation or node switch minutes after boot reported page age as the foreground hydration duration. The developer-mode report now distinguishes boot-, node-session-, and attempt-relative durations (schema v2), the collapsed chip shows the attempt-relative foreground value with its anchor, and superseded, aborted, or failed attempts can never become the reported success. The report schema bumps to v2 with explicit anchors: bootAgeMs and the boot->auth/nodes/shell durations, sessionAgeMs and session-relative list visible/hydrated, and lastAttempt* fields resolved from the newest committed list_visible event in the active node session (event-derived, so a later stack-detail attempt cannot steal the headline). listVisibleMs is retained as the raw boot-relative compatibility field. The repeatable performance baseline scenario matrix is documented in the local internal docs (docs/internal/performance/hydration-timing-baseline.md); the post-hot-path-fix baseline capture is a follow-up evidence step.
This commit is contained in:
@@ -4,10 +4,15 @@ import type { HydrationReport, HydrationSnapshot } from '@/lib/hydrationTiming';
|
||||
|
||||
let mockSnapshot: HydrationSnapshot;
|
||||
let mockListVisibleMs: number | null;
|
||||
let mockListAnchor: 'attempt' | 'session' | null;
|
||||
let mockReport: HydrationReport;
|
||||
|
||||
vi.mock('@/hooks/useHydrationTiming', () => ({
|
||||
useHydrationTiming: () => ({ snapshot: mockSnapshot, listVisibleMs: mockListVisibleMs }),
|
||||
useHydrationTiming: () => ({
|
||||
snapshot: mockSnapshot,
|
||||
listVisibleMs: mockListVisibleMs,
|
||||
listAnchor: mockListAnchor,
|
||||
}),
|
||||
}));
|
||||
|
||||
const clearReportMock = vi.fn();
|
||||
@@ -23,25 +28,39 @@ import { HydrationTimingPanel } from '../HydrationTimingPanel';
|
||||
|
||||
function snapshot(events: HydrationSnapshot['events'] = []): HydrationSnapshot {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
clock: 'performance.now',
|
||||
bootSessionId: 'boot-1',
|
||||
bootStartAt: 0,
|
||||
nodeSessionId: 'node-2',
|
||||
nodeId: 1,
|
||||
nodeSessionStartAt: 0,
|
||||
lastAttempt: null,
|
||||
events,
|
||||
};
|
||||
}
|
||||
|
||||
function report(over: Partial<HydrationReport> = {}): HydrationReport {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
schemaVersion: 2,
|
||||
capturedAt: 0,
|
||||
clock: 'performance.now',
|
||||
bootSessionId: 'boot-1',
|
||||
nodeSessionId: 'node-2',
|
||||
nodeId: 1,
|
||||
listVisibleMs: 1200,
|
||||
bootAgeMs: 620000,
|
||||
bootAuthResolvedMs: 200,
|
||||
bootNodesResolvedMs: 400,
|
||||
bootShellCommittedMs: 500,
|
||||
sessionAgeMs: 4200,
|
||||
sessionListVisibleMs: 1200,
|
||||
sessionListHydratedMs: 1500,
|
||||
lastAttemptId: 'attempt-1',
|
||||
lastAttemptListVisibleMs: 420,
|
||||
lastAttemptListHydratedMs: 700,
|
||||
lastAttemptHydrationGapMs: 280,
|
||||
lastAttemptProxied: null,
|
||||
lastAttemptNodeId: null,
|
||||
anyProxied: false,
|
||||
phases: [
|
||||
{ phase: 'boot_start', kind: 'milestone', offsetMs: 0, critical: true, outcome: 'ok' },
|
||||
@@ -55,18 +74,27 @@ beforeEach(() => {
|
||||
clearReportMock.mockClear();
|
||||
copyMock.mockClear();
|
||||
mockSnapshot = snapshot();
|
||||
mockListVisibleMs = 1200;
|
||||
mockListVisibleMs = 420;
|
||||
mockListAnchor = 'attempt';
|
||||
mockReport = report();
|
||||
});
|
||||
|
||||
describe('HydrationTimingPanel', () => {
|
||||
it('shows the list_visible elapsed time on the collapsed chip', () => {
|
||||
it('shows the foreground list_visible elapsed time and its anchor on the collapsed chip', () => {
|
||||
render(<HydrationTimingPanel />);
|
||||
expect(screen.getByTestId('hydration-chip')).toHaveTextContent('list 1.2s');
|
||||
expect(screen.getByTestId('hydration-chip')).toHaveTextContent('list 420ms · attempt');
|
||||
});
|
||||
|
||||
it('shows the session anchor when no foreground attempt exists', () => {
|
||||
mockListAnchor = 'session';
|
||||
mockListVisibleMs = 100;
|
||||
render(<HydrationTimingPanel />);
|
||||
expect(screen.getByTestId('hydration-chip')).toHaveTextContent('list 100ms · session');
|
||||
});
|
||||
|
||||
it('shows an ellipsis before list_visible commits', () => {
|
||||
mockListVisibleMs = null;
|
||||
mockListAnchor = null;
|
||||
render(<HydrationTimingPanel />);
|
||||
expect(screen.getByTestId('hydration-chip')).toHaveTextContent('list …');
|
||||
});
|
||||
@@ -83,6 +111,13 @@ describe('HydrationTimingPanel', () => {
|
||||
expect(screen.getByTestId('hydration-chip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows boot age and session age as context alongside the chip', () => {
|
||||
render(<HydrationTimingPanel />);
|
||||
fireEvent.click(screen.getByTestId('hydration-chip'));
|
||||
expect(screen.getByText(/Boot age/)).toHaveTextContent('620.0s');
|
||||
expect(screen.getByText(/Session age/)).toHaveTextContent('4.2s');
|
||||
});
|
||||
|
||||
it('collapses on Escape', () => {
|
||||
render(<HydrationTimingPanel />);
|
||||
fireEvent.click(screen.getByTestId('hydration-chip'));
|
||||
|
||||
Reference in New Issue
Block a user